Testing View Model IoC Resolution in MvvmCross

To be honest, most of the time there's no need to use an IoC container in unit tests. You actually want to manually provide specific implementations or even mocks of dependencies to class constructors in individual tests. Often that's even one of the reasons for using dependency injection and IoC frameworks in the first place: to be able to provide a different testing implementation from the production one.

Still, testing the IoC container configuration makes sense in an application, even though that's more an integration test than a unit test. When using IoC containers, we must be aware that runtime errors will occur when not all required dependencies for the created object are registered in the application beforehand. Since dependencies can change through time, having tests to make sure all the types can be instantiated, will give us additional assurance, that our application works as expected. Of course, this doesn't necessarily mean, the right implementations are being used for all dependencies, but that's not what we want to test.

Using MvvmCross's built-in IoC in unit tests is not all that simple, though. There's a helper base class available to make it easier, but unfortunately only for .NET 4.5. In our case this won't be enough, because we want to test dependency registration in each platform specific application and we can't do that by just testing a portable class library from within a .NET 4.5 test project. I even tried using the code from the base class in a portable or platform specific base class, but it also turned out to be the wrong approach. The test would only use the registrations from the portable Application class, but there are other locations where dependency registration is being performed, such as plugins and the platform specific Setup class.

This forced me to test the actual platform specific code by referencing it in the test project and calling its initialization from the test. This was my first attempt (the sample is Windows Store specific, because this is the platform, I am targeting with my current project):

[UITestMethod]
public void CanResolveMyViewModel()
{
  MvxSingleton.ClearAllSingletons();
  var ioc = MvxSimpleIoCContainer.Initialize();
  ioc.RegisterSingleton(ioc);
  ioc.RegisterSingleton<IMvxSettings>(new MvxSettings());

  var setup = new Setup(new Frame());
  setup.Initialize();

  var viewModel = Mvx.IocConstruct<MyViewModel>();
  Assert.IsNotNull(viewModel);
}

Notice the call to Setup constructor. It requires an instance of a Frame, which is a UI object and must be run on the UI thread; and tests by default aren't. Providing a null instead, causes an exception. That's the reason I had to use UITestMethod attribute instead of TestMethod. This works with most test runners, but unfortunately my favorite one, NCrunch, doesn't recognize such methods as tests. I was able to work around this by using a different technique to execute the problematic code on the UI thread:

[ClassInitialize]
public static async Task Init(TestContext context)
{
  var taskSource = new TaskCompletionSource<object>();
  await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
    CoreDispatcherPriority.Normal, () =>
    {
      try
      {
        // Frame must be instantiated and used on UI thread
        var setup = new Setup(new Frame());
        setup.Initialize();

        taskSource.SetResult(null);
      }
      catch (Exception e)
      {
        taskSource.SetException(e);
      }
    });
  await taskSource.Task;
}

[TestMethod]
public void CanResolveMyViewModel()
{
  var viewModel = Mvx.IocConstruct<MyViewModel>();
  Assert.IsNotNull(viewModel);
}

As you can see, I even managed to wrap all my initialization code into a separate method, called by MSTest framework only once before all the other tests in the class. This way no initialization code needs to be put in the test methods, making them easy to write and understand.

Copyright
Creative Commons License