Unit testing with WindowsPhoneMvp

For a little while now, I've been working on two projects along with WP7 development, the first is a WP7 version of Autofac, and the second is WindowsPhoneMvp. Testing looks to be something that is mostly forgotten in WP7, but also on this note, why would you use a WP7 framework that didn't allow you to improve the testability of your code? So here is a small sample of how to get started with WindowsPhoneMvp 0.9.0.3.

Creating unit tests

To get started, get a new testing project for WindowsPhone up and running, using the guide here.

For this guide I'll be using the NUnit SL3 project. The reason being I've had a few issues with the MS Test provider actually finding and running tests.

The "ConcernFor" BDD pattern

(Read more about it here)
To avoid setting up contextual goo everywhere, I've had a reasonable amount of success using the ConcernFor pattern. To use this, start with a base class like the following:

using NUnit.Framework;

namespace DemoWindowsPhoneMvpTests.Tests
{
    public abstract class ConcernFor<T>
    {
        protected T Subject;

        [SetUp]
        public void Setup()
        {
            Context();
            Subject = CreateSubjectUnderTest();
            Because();
        }

        public abstract void Context();
        public abstract T CreateSubjectUnderTest();
        public abstract void Because();
    }
}


Mocking on WP7
With the lack of all good .net proxy providers, mocking can be a bit of a hassle, which is why WindowsPhoneMvp now comes with a "Mocks" library to help during unit testing. The Mocks library provides mock implementations for your presenters that you can use for unit testing and validating assumptions.

So after including the WindowsPhoneMvp.Mocks library into your testing project, I usually make another testing base class for presenter testing that can setup the Mock providers. Here is what that class might look like:

using System.Collections.Generic;
using WindowsPhoneMvp;
using WindowsPhoneMvp.Mocks.Phone.State;
using WindowsPhoneMvp.Mocks.Navigation;
using NUnit.Framework;
using WindowsPhoneMvp.Mocks.Phone;

namespace DemoWindowsPhoneMvpTests.Tests
{
    public abstract class PresenterConcernFor<T, TView> : ConcernFor<T>
        where T : Presenter<TView>
        where TView : class, IView
    {
        protected MockNavigationService Navigation = new MockNavigationService();
        protected MockTransientState ApplicationState = new MockTransientState();
        protected MockTransientState State = new MockTransientState();
        protected MockTaskManager TaskManager = new MockTaskManager();
        protected IDictionary<string, string> NavigationParams = new Dictionary<string, string>();

        protected void SetupMocksFor(T presenter)
        {
            presenter.ApplicationTransientState = ApplicationState;
            presenter.TransientState = State;
            presenter.Params = NavigationParams;
            presenter.TaskManager = TaskManager;
            presenter.Messages = new MessageCoordinator();
        }

        [TearDown]
        public void CleanUp()
        {
            Subject.ReleaseView();
        }
    }
}


Mocking the View

Mocking the view we will using during testing is fairly simple, and to make it easier the Mocks project also provides you with a base MockView to inherit from.

using System;
using DemoWindowsPhoneMvp.Logic.Views.Main;
using WindowsPhoneMvp.Mocks;

namespace DemoWindowsPhoneMvpTests.Tests.Main
{
    public class MainViewMock : MockView, IMainView
    {
        public event EventHandler LaunchingCamera = delegate { };

        public void InvokeLaunchCamera()
        {
            LaunchingCamera(this, EventArgs.Empty);
        }
    }
}

Writing tests

Now on to actually writing tests. In the demo project I want to test the MainPresenter, so I write another base class specifically for this task.

public abstract class MainPresenterBase : PresenterConcernFor<MainPresenter, IMainView>
{
    protected MainViewMock MainView = new MainViewMock();

    public override MainPresenter CreateSubjectUnderTest()
    {
        var presenter = new MainPresenter(MainView, Navigation);
        SetupMocksFor(presenter);
        return presenter;
    }

    public override void Context()
    {

    }
}

The only parts of the ConcernFor I have not fulfilled at this point are "Context", "Because" and the Assertions, however all your following tests can now focus solely on these three things.

[TestFixture]
public class When_main_presenter_is_loaded : MainPresenterBase
{
    [Test]
    public void Then_persist_me_property_has_been_set()
    {
        Assert.AreEqual("test", Subject.PersistMe);
    }

    [Test]
    public void Then_PageState_test_has_been_set()
    {
        Assert.AreEqual("RememberMeOnThisPage", State.Get("PageState"));
    }

    public override void Because()
    {
        MainView.InvokeLoad();
    }
}

And other example with Navigation.

[TestFixture]
public class When_navigating_to_view_two : MainPresenterBase
{
    [Test]
    public void Then_the_correct_view_name_was_called()
    {
        Assert.AreEqual("ISecondView", Navigation.NavigationResult.View);
    }

    public override void Because()
    {
        Subject.Model.NavigatingToViewTwo.Execute(null);
    }
}

And even mock the camera task

[TestFixture]
public class When_camera_has_been_launched : MainPresenterBase
{
    [Test]
    public void Then_the_photo_result_is_not_null()
    {
        Assert.NotNull(Subject.PhotoResult);
    }

    public override void Context()
    {
        TaskManager.Returns = x =>
                                  {
                                      return x == typeof(CameraCaptureTask) ? new PhotoResult() : null;
                                  };
    }

    public override void Because()
    {
        MainView.InvokeLaunchCamera();
    }
}

And heres an example of the output we get, nice and readable:

alt WindowsPhoneMvp Tests

WP7 WindowsPhoneMvp UnitTesting
Posted by: Brendan Kowitz
Last revised: 21 Sep 2013 12:13PM

Comments

No comments yet. Be the first!

No new comments are allowed on this post.