Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test MvvmCross with IMvxMessenger

I'm using Xamarin Studio to embark upon TDD using MvvmCross. I'm trying to first start by testing the effect of messages being published to the view model so that logic is performed only when the appropriate message is recieved.

I've hacked together some of Stuart's excellent tutorials resulting in successful propogation of location data to view models which then update some text controls, map markers, etc on the IOS views.

But before I dive in further I want to code using TDD. How do I artificially setup the viewmodel and artificially publish the messages to it in my test harness? :

public class MyViewModel : MvxViewModel
{
    private readonly MvxSubscriptionToken _token;

    public MyViewModel(ILocationService service, IMvxMessenger messenger)
    {
        //weak reference
        _token = messenger.Subscribe<LocationMessage>(OnLocationMessage);
    }

    private void OnLocationMessage(LocationMessage locationMessage)
    {
        Lat = locationMessage.Lat;
        Lng = locationMessage.Lng;
        // Console.WriteLine("on loc msg {0:0.0000}, {1:0.0000}", Lat, Lng);
    }

    private double _lng;
    public double Lng
    {
        get { return _lng; }
        set
        {
            _lng = value;
            RaisePropertyChanged(() => Lng);
        }
    }

    private double _lat;
    public double Lat
    {
        get { return _lat; }
        set
        {
            _lat = value;
            RaisePropertyChanged(() => Lat);
        }
    }
}


[TestFixture()]
public class LocTest
{
    [Test()]
    public void LocationMessageIsRecieved()
    {
        // im using nsubstitute to mock with
        var locService = Substitute.For<ILocationService>();  
        var msgr = Substitute.For<IMvxMessenger>();
        var vm = new Map2ViewModel(locService, msgr);

        var locMsg = new LocationMessage(this, 1F, 2F);
        msgr.Publish(locMsg);

        var lat = vm.Lat;
        Assert.AreEqual(2F, lat);  // says lat is 0.0  and nunit doesnt let me debug the tests :(
    }
}

Any great tutorials on TDD with MvvmCross would be fantastic

like image 825
fractal Avatar asked Dec 15 '22 04:12

fractal


2 Answers

Any great tutorials on TDD with MvvmCross would be fantastic

Greg Shackles's talk at Evolve is a very good starting place for this - http://xamarin.com/evolve/2013#session-7wb0etd3r8

His CodeCamp example contains a superb set of unit test examples - http://www.gregshackles.com/2013/09/nyc-code-camp-8-mobile-apps/ leading to https://github.com/gshackles/NycCodeCamp8/tree/master/CodeCamp.Core/tests/CodeCamp.Core.Tests/ViewModelTests

A tutorial on MvvmCross Unit Testing - including Mocking - is available in N=29 on http://mvvmcross.wordpress.com/

A blog post is also available on http://blog.fire-development.com/2013/06/29/mvvmcross-enable-unit-testing/

like image 163
Stuart Avatar answered Dec 27 '22 11:12

Stuart


How do I artificially setup the viewmodel and artificially publish the messages to it in my test harness? :

After setting up as described in the last link that Stuart posted, the pattern I have used to test MvxMessenger is to use Moq (along with AutoFixture) to create a mock IMvxMessenger and inject it:

_mockMvxMessenger = Fixture.Freeze<Mock<IMvxMessenger>> ();
_myViewModel = _fixture.Build<MyViewModel ().OmitAutoProperties().Create ();

The above assumes you inject an IMvxMessenger into your ViewModel.

If you need to check that a message has been published, you can assert (verify) on the mock

_myViewModel.MyCommand.Execute (null);
_mockMvxMessenger.Verify (m => m.Publish (It.IsAny<MyMvxMessage>()), Times.Once);

If you need to trigger a message, then grab the subscription Action and fire it when you like

Do this after freezing the Mock but before building the ViewModel:

private Action<MyMvxMessage> _callbackAction; // class scope var
_mockMvxMessenger.Setup (n => n.Subscribe<MyMvxMessage> (It.IsAny<Action<MyMvxMessage>> (), It.IsAny<MvxReference>(), It.IsAny<string>())).Callback<Action<MyMvxMessage>, MvxReference, string> ((action,mvxref,tag) => _callbackAction = action);

Then in your test you can 'fire the message' by just calling

_callbackAction(new MyMvxMessage(this));
like image 27
Ryan Avatar answered Dec 27 '22 11:12

Ryan