Prism (CAL) unit testing – How to test Prism (CAL) Event Aggregator using Rhino Mocks

I spent some time recently working with Microsoft Composite Application Guidance (A.K.A. "Prism", “CAL”) and I think it is very good platform for building composite UI by either using WPF or Silverlight.

One of its greatest advantages is that it was done in open source manner which resulted with most of the community feedback being incorporated into lightweight, testing friendly framework. Reference implementation and samples are also good but showing only static stubs based testing which is ok but not as powerful as mocking with some mocking framework.

My mocking framework of choice is Rhino Mocks and I am going to make couple of simple blog posts showing how to test Prism code using the Rhino Mocks in couple of typical every day scenarios.

And that leads us to today’s blog post…

How to test Prism (CAL) EventAggregator based code using Rhino Mocks?

imageSUT I’ll be using today will be as simple as possible to deliver the message.

LoggingService is service which is responsible for handling the logging of system errors in a way that:

  • subscribes to system wide events which are requested to be logged without referencing the event sources
  • decides if event needs to be published (based on severity)
  • if it does, it formats the system event adding the time stamp etc
  • calls the publishing service which is handling the publishing of formatted event

Considering the fact that in my sample we would be having various publishing services in system publishing to different targets(eventlog, flat text file) the LoggingService gets dependency injected only a component implementing the IPublishingService.

Subscription to system wide events on a decoupled manner is possible through EventAggregator design pattern  which implementation is in Prism provided through IEventAggregator service.

The LogErrorEvent is an event to which LoggerService would subscribe and which carries the event argument of EventData type containing the ErrorLevel and ErrorMessage data.

Show me the code

Enough of my blabbering (and my English), code will speak for itself much better 🙂

Sample used in today’s blog post can be downloaded here.

LoggingService

using System;
using System.Text;
using Microsoft.Practices.Composite.Events;

namespace Example
{
    public class LoggingService
    {
        private readonly IEventAggregator eventAggregator;
        private readonly IPublishingService publishingService;

        public LoggingService(IEventAggregator eventAggregator, IPublishingService publishingService)
        {
            this.eventAggregator = eventAggregator;
            this.publishingService = publishingService;
            this.eventAggregator
                .GetEvent<LogErrorEvent>()
                .Subscribe(this.LogIt);
        }

        private void LogIt(EventData eventData)
        {
            if (eventData.ErrorLevel<=100)
                return;

            var stringBuilder = new StringBuilder();
            stringBuilder
                .AppendFormat("Date:{0}", DateTime.Now)
                .AppendLine()
                .Append(eventData.ErrorMessage)
                .AppendLine();
            this.publishingService.PublishError(stringBuilder.ToString());
        }
    }
}

Nothing fancy there:

  • constructor accepts two parameters which provide to LoggingService access to event aggregator and publishing service through inversion of controls.
  • in constructor injected event aggregator is used for subscribing of LogIt method to LogErrorEvent.
  • LogIt method is private (wouldn’t work in case of Silverlight – but that is sepa rate blog post) and does next things:
    • makes sure that only events with level greater then 100 get published
    • formats the given error message into appropriate format
    • pass the formatted message to publishing service

 

IPublishingService, LogErrorEvent and EventData

namespace Example
{
    public interface IPublishingService
    {
        void PublishError(string errorMessage);
    }
}

using Microsoft.Practices.Composite.Presentation.Events;

namespace Example
{
    public class LogErrorEvent : CompositePresentationEvent
    {

    }
}

namespace Example
{
    public class EventData
    {
        public int ErrorLevel { get; set; }
        public string ErrorMessage { get; set; }
    }
}

No need to waste time commenting this…

Testing the code

I could have done test first etc, but I believe that it would obfuscate the point of this blog , which now once we see the code being tested is going to be just showing the tests 

Test 1 – How to make sure that event is getting subscribed

        /// 
        /// Shows how to verify that event aggregator subscription occurred.
        ///
        [TestMethod()]
        public void Ctor_Default_WouldSubscribeToLogErrorEvent()
        {
            // arrange
            var publishingServiceStub = MockRepository.GenerateStub<IPublishingService>();
            var eventAggregatorMock = MockRepository.GenerateStub<IEventAggregator>();
            var logErrorEvent = MockRepository.GenerateMock<LogErrorEvent>();

            // event aggregator get event would return mocked log error event
            eventAggregatorMock.Stub(p => p.GetEvent<LogErrorEvent>()).Return(logErrorEvent);

            // expect that LogErrorEvent would be subscribed in constructor
            logErrorEvent
                .Expect(p => p.Subscribe(null))
                .Return(null)
                .IgnoreArguments() // we don't care which exact method or action subscribed, just that there was some.
                .Repeat.Once();

            // act
            var loggingService = new LoggingService(eventAggregatorMock, publishingServiceStub);

            // assert
            logErrorEvent.VerifyAllExpectations();
        }

The test is using Rhino Mocks AAA syntax introduced in 3.5 version (if you don’t know it, read Rhino Mocks Documentation Wiki excellent documentation).

In Arange section test:

  • defines two stubs for the services being used (stubs because I don’t care to set any expectation related to them in this test)
  • defines the mock of the event which subscription I am about to check
  • stubs the event aggregator behavior so on GetEvent<LoggErrorEvent>() method call would return event mock I created.
  • defines expectation on that event mock that subscription would occur once (IgnoreArguments() is there because this test doesn’t care really which method exactly would subscribe to event. Test cares only that subscription had occurred)

In Act section test just constructs the service

In Assert section test triggers verifying of the event log expectations (which in this test were: someone subscribed to this event)

(Note that making test for verifying that the Publish have occurred during the test would be pretty much the same as this test with a change on mocked expectations only)

Test 2a – How to invoke event aggregator in Act test section

Sometimes there is a behavior we want to unit test which is occurring upon the event being published through IEventAggregator and because we can be using anonymous delegate, private method handling the event (case of this blog post) there is no easy way to invoke functionality which is wanted to be tested.

This test shows how to invoke event aggregator to publish the desired event which would trigger code being tested,

In case of this example we want to test that not severe errors (error level <= 100) are not getting published.

        /// 
        /// An example of how to trigger event aggregator in act section
        ///
        [TestMethod()]
        public void LogIt_ErrorLevel100_WouldNotBePublished()
        {
            // arrange
            var logErrorEvent = new LogErrorEvent();
            var publishingServiceMock = MockRepository.GenerateMock<IPublishingService>();
            var eventAggregatorStub = MockRepository.GenerateStub<IEventAggregator>();

            eventAggregatorStub.Stub(p => p.GetEvent<LogErrorEvent>()).Return(logErrorEvent);

            // expect that publishing service would never be called
            publishingServiceMock
                .Expect(p => p.PublishError(Arg<string>.Is.Anything))
                .Repeat.Never();

            // act
            var loggingService = new LoggingService(eventAggregatorStub, publishingServiceMock);

            // invoke the event aggregator
            logErrorEvent.Publish(new EventData()
            {
                ErrorLevel = 100,
                ErrorMessage = "Some error message"
            });

           // assert
            publishingServiceMock.VerifyAllExpectations();
        }

In this test, test is having in Arrange section:

  • An instance of the log error event (not the mock of that event like in the case of previous test example)
  • Mock of the publishing service (in previous test we had stub) because that is service we need to check it won’t be called.
  • Stub of the event aggregator with stubbed GetEvent<LogErrorEvent> method
  • An expectation that PublishError method of the publishing service would never be called (regardless of the parameter being sent to that method)

In Act section test is :

  • constructing the logging service injecting the event aggregator stub and mock of publishing service
  • invoking the Publish method on a log error event instance passing the test data (error level == 100 in this test)

In Assert section, we are just triggering checking of expectation defined on mock of the publishing service (no call made to publish error method)

Test 2b – How to invoke event aggregator in Act test section

Although from perspective of this blog post it doesn’t have a lot of value here’s the test testing that publishing service will be called in case event will be published with error level greater then 100. (The more Rhino Mocks examples we have on web, the better adopting rate will be :))

	/// 
        /// An example of how to trigger event aggregator in act section
        ///
        [TestMethod()]
        public void LogIt_ErrorLevelGreaterThen100_WouldBePublished()
        {
            // arrange
            var logErrorEvent = new LogErrorEvent();
            var publishingServiceMock = MockRepository.GenerateMock<IPublishingService>();
            var eventAggregatorStub = MockRepository.GenerateStub<IEventAggregator>();

            eventAggregatorStub.Stub(p => p.GetEvent<LogErrorEvent>()).Return(logErrorEvent);

            publishingServiceMock
                .Expect(p => p.PublishError(Arg<string>.Matches(param => param.Contains("Some error message"))))
                .Repeat.Once();

            // act
            var loggingService = new LoggingService(eventAggregatorStub, publishingServiceMock);
            logErrorEvent.Publish(new EventData()
            {
                ErrorLevel = 101,
                ErrorMessage = "Some error message"
            });
            // assert
            publishingServiceMock.VerifyAllExpectations();
        }

Almost the same sample like previous one just with two small differences:

  • In arrange section, expectation is that the PublishError method would be called once with a method parameter containing “Some error message” string (inline constrains)
  • In act section, event is published with error level >100 to trigger the positive case when publishing service is been triggered

Green is nice 🙂

image

Conclusion

Thanks to P&P team and community feedback, CALPRISM event aggregator is implemented in such a way that mocking it is very easy (if not trivial) and every framework enabling easy testing is a good framework for me 🙂 Good work P&P!

Advertisements

Posted on April 18, 2009, in Uncategorized. Bookmark the permalink. 3 Comments.

  1. Very nice work, I want to point out though, that this does not work if you are subscribing with "ThreadOption.UIThread".  I am still trying to find a way to test in this situation.

  2. Is there any fix for the issue , related to "ThreadOption.UIThread".  

  3. Hi I am trying to achieve the same using Moq but struggling how you done it using moq?can you help
    dotnet@devnet247.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: