Creating unit tests

To get started, get a new testing project for WindowsPhone up and running, using the guide at http://www.smartypantscoding.com/a-cheat-sheet-for-unit-testing-silverlight-apps-on-windows-phone-7

For this guide I'll be using the NUnit SL3 project http://code.google.com/p/nunit-silverlight/. The reason being I've had a few issues with the MS Test providing finding tests.

The "ConcernFor" pattern

(Read more about it here: http://www.davesquared.net/2009/10/calculators-and-tale-of-two-tdds-pt-2.html)
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
    [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();
        }
    }

Last edited Mar 20, 2011 at 12:56 AM by brendan, version 2

Comments

No comments yet.