Unit test services with mock frameworks approach?

Developer
Nov 22, 2011 at 3:11 PM

With the approach described on the documentation, topic “Unit testing services” I have to write a mock class in order and mapping on the app.config. I order to avoid write this mock classes, mocks  framework allow you create dynamics proxies with configurable results on the fly.

I’m trying to test my view models mocking, the services interfaces (eg: IMessageService) using a mock framework but doesn’t work as I expect. I'm using NUnit.Silverlight and Moq.

My test implementation looks as follow:

[Setup]
void Init()
{
    this.container = new UnityContainer();
    ServiceLocator.Instance.RegisterExternalcontainer(this.container);
}

[Test]
void MessageServiceTest()
{
    messageServiceMock.Setup(service => service.Show(It.IsAny<string>(), It.IsAny<string>(), It.Is<MessageButton>(button => button == MessageButton.OKCancel), It.Is<MessageImage>(image => image == MessageImage.Exclamation))).Returns(MessageResult.OK);
    this.container.RegisterInstance(messageServiceMock.Object);
    var viewModel = new TryToMockMessageServiceViewModel();
    viewModel.DisplayMessageCommand.Execute();
    Assert.IsTrue(...)
}

So when I try to resolve the IMessageService instance on the ViewModel implementation, I get the default the Catel default IMessageService implementation instead the mock instance registered later on the test.

private void ExecuteDisplayMessageCommand()
{
    var messageService =  this.GetService<IMessageService>(); // Here I get the Catel default IMessageService implementation instead the mock instance registered later on the Test.
    if (messageService.Show("Are you sure?", "Alert", MessageButton.OKCancel, MessageImage.Exclamation) == MessageResult.Yes)
    {
      ...
    }
}

Have I an error on my test implementation?

Are there a reason for Catel doesn't support this approach using mock frameworks?

Developer
Nov 22, 2011 at 3:17 PM

There are another problem with configuration file mapping approach, Microsoft Unity 2.0 for Silverlight doesn't support XML configuration see http://msdn.microsoft.com/en-us/library/ff678312.aspx.

Coordinator
Nov 22, 2011 at 3:20 PM

On the creation of the view model, Catel registers the missing services if they are not yet registered. So, probably the constructor of the view model is done earlier than the registration of the external container. Are you sure the right type is registered in the container? And you are sure the proxy object implements IMessageService?

Developer
Nov 22, 2011 at 4:21 PM

On the example above I miss the line

   var messageServiceMock = new Mock<IMessageService>();

You have right, on my real test I have written the mock registration after view the model initialization.

But another problem emerge. ServiceLocator.Instance is the access point to the single instance of the Catel ServiceLocator, then the initialization occurs only once on the application life cycle (I think) and I have no control over the ServiceLocalor.Instance.

Therefore, if another test that initialize any view model run before the one where I register the mock service then the registration on the unity container do not override the existing registration of any service on the service locator.

I need to update the service mock registration between test.

  • Are there a way to override an existing registered instance on the service locator via container synchronization?
  • Are there a way to override an existing registered instance on the service locator?
  • Are there a way to "re-initialize" the ServiceLocator.Instance between test?
Coordinator
Nov 22, 2011 at 4:31 PM

There is only one service locator, and in behaves like a singleton. The service locator supports re-registration of a service, but only on the ServiceLocator itself (it doesn't happen very often that you want to change an implementation once registered).

  1. Not via external containers (since not all external containers support that)
  2. Yes, a call to serviceLocator.RegisterInstance does overwrite old registrations once they are instantiated.
  3. No, this is not yet possible, I have created a work item for this (see http://catel.codeplex.com/workitem/7034)

There is one other option: it is possible to inject custom services into a specific view model instance. The downside is that you have to create a constructor override on your view models to allow this. However, this is also be the case if a custom ServiceLocator instance can be injected.

Just a small note: you know that there is a mock implementation of every service available in Catel, right? You could just use that.

Developer
Nov 22, 2011 at 4:33 PM

The found a workaround. Just register directly on the service locator instance the mock service one.

  ServiceLocator.Instace.RegisterInstance(messageServiceMock.Object);

Thanks.

Developer
Nov 22, 2011 at 4:40 PM

I don't knew it about the buil-in mock catel service implementation.

I will review it.

Thanks again.

Coordinator
Nov 22, 2011 at 4:42 PM

They are all located in the Catel.MVVM.Services.Test namespace, see the following page for the MessageService:

http://catel.catenalogic.com/index.html?t_catel_mvvm_services_test_messageservice.htm

You can enqueue expected messages like this:

 

Test.MessageService service = (Test.MessageService)ServiceLocator.Instance.ResolveType<IMessageService>();

// Queue the next expected result
service.ExpectedResults.Add(MessageResult.Yes);

Coordinator
Nov 26, 2011 at 4:23 PM

FYI: the next version of Catel (will be released soon) will have a constructor overload with a ServiceLocator instance. This way, you can set up your own service locator per view model and inject it into the view model.