fd Blog

Daniel Hilgarth on software development

MvvmCross: Unit-testing With AutoFixture

In the previous post I gave some general hints regarding unit-testing an MvvmCross application.
Now I want to give some hints on how to make it work when using AutoFixture.
Please note that reading the previous post is encouraged, because this post references it repeatedly.

Example test and default AutoFixture infrastructure

The following test should serve as an example on how such a test could look like:

[Theory, AutoTestData]
public void IfLoginSucceeds_ShowGreetingOverview(
    IMvxViewDispatcher viewDispatcher,
    UserModel user,
    LoginViewModel sut)
{
    var loginHandler = (ILoginHandler)sut;

    loginHandler.OperationSucceeded(user);

    var expectedRequestSpecification =
        Arg.Is<MvxViewModelRequest>(x => x.ViewModelType == typeof(GreetingOverviewViewModel));
    viewDispatcher.Received().ShowViewModel(expectedRequestSpecification);
}

This test is straight forward:
It verifies that ShowViewModel on viewDispatcher has been called with the correct ViewModel type in response to a call to OperationSucceeded. As you can see, it uses AutoData Theories to create the data used in the test.
The AutoTestData attribute is the project-specific implementation of this feature and uses our own Customization:

public class AutoTestDataAttribute : AutoDataAttribute
{
    public AutoTestDataAttribute()
        : base(new Fixture().Customize(new TestCustomization()))
    {
    }
}

TestCustomization is the actual Customization that configures AutoFixture according to our needs:

public class TestCustomization : CompositeCustomization
{
    public TestCustomization()
        : base(
            new AutoNSubstituteCustomization(),
            new AnonymousCreationCustomization(),
            new MvvmCrossTestSetupCustomization())
    {
    }

    private class AnonymousCreationCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            // ...
        }
    }
}

This class has a default layout as used in many other test projects.
The interesting part with regards to MvvmCross is the MvvmCrossTestSetupCustomization we pass in.

Create a Customization that registers the Singletons with MvvmCross and AutoFixture

The next step is to create MvvmCrossTestSetupCustomization:

public class MvvmCrossTestSetupCustomization : ICustomization, ISingletonRegisterer
{
    private IFixture _fixture;

    public void Customize(IFixture fixture)
    {
        _fixture = fixture;
        var dispatcher = fixture.Create<IMvxViewDispatcher>();
        fixture.Inject(dispatcher);
        fixture.Inject<IMvxMainThreadDispatcher>(dispatcher);
        MvvmCrossTestSetup.Execute(this);
    }

    public void RegisterSingletons(IMvxIoCProvider ioc)
    {
        var dispatcher = new MockMvxViewDispatcher(_fixture.Create<IMvxViewDispatcher>());

        ioc.RegisterSingleton(dispatcher);
        ioc.RegisterSingleton<IMvxMainThreadDispatcher>(dispatcher);
    }
}

This customization performs the following steps:

  1. Create an anonymous instance of IMvxViewDispatcher. That’s the interface we want to register a Singleton for.
    This instance will actually be a mock created by NSubstitute, because our TestCustomization passes AutoNSubstituteCustomization as the first Customization to its base class constructor.
  2. The created instance is injected back into AutoFixture for both IMvxViewDispatcher and IMvxMainThreadDispatcher.
    Injecting an instance into AutoFixture will basically make this a Singleton instance for AutoFixture, i.e. all future requests for this type will return the same instance.
  3. Use a slightly modified version of MvvmCrossTestSetup - from the last post - to setup MvvmCross.
  4. Create an instance of MockMvxViewDispatcher - also from the last post - that decorates our actual mock from step 1.
    Remember:
    • Because we used _fixture.Inject, the call to _fixture.Create inside RegisterSingletons will return the same instance we injected.
    • We have to create an instance of MockMvxViewDispatcher because that’s what actually registers it as a Singleton with MvvmCross.
  5. Register the created instance of MockMvxViewDispatcher with the Service Locator from MvvmCross.

For reference, the modified MvvmCrossTestSetup now looks like this:

public static class MvvmCrossTestSetup
{
    public static void Execute()
    {
        Execute(new NullSingletonRegisterer());
    }

    public static void Execute(ISingletonRegisterer singletonRegisterer)
    {
        MvxSingleton.ClearAllSingletons();
        var ioc = MvxSimpleIoCContainer.Initialise();
        ioc.RegisterSingleton(ioc);
        ioc.RegisterSingleton<IMvxTrace>(new TestTrace());
        singletonRegisterer.RegisterSingletons(ioc);
        InitialiseSingletonCache();
        InitialiseMvxSettings(ioc);
        MvxTrace.Initialize();
    }

    private static void InitialiseMvxSettings(IMvxIoCProvider ioc)
    {
        ioc.RegisterSingleton<IMvxSettings>(new MvxSettings());
    }

    private static void InitialiseSingletonCache()
    {
        MvxSingletonCache.Initialise();
    }

    private class TestTrace : IMvxTrace
    {
        /* unchanged ... */
    }
}

It has been changed to a static class and the Execute method takes an instance of ISingletonRegisterer. On this instance it calls RegisterSingletons with the instance of the Service Locator it just created.
The method RegisterSingletons in our MvvmCrossTestSetupCustomization is an implementation of that interface method.

Conclusion

If you are familiar with AutoFixture and have read my previous post about unit-testing MvvmCross nothing I showed in this post should come as a surprise.

Comments