Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking method with Action<T> parameter

[unit testing newbie] [c#]

Consider the following scenario:

I'm using Silverlight and calling a WCF service. Silverlight can only call WCF services asynchronously. I build a wrapper around the WCF service so that I can work with Action parameters. (makes the client code a lot cleaner).

So I have an async service that retrieves meeting rooms.

public interface IMeetingRoomService
{
    void GetRooms(Action<List<MeetingRoom>> result);
}

Turning GetRooms into List<MeetingRoom> GetRooms() is not an option.

I want to use this service in a ViewModel to set a public property called Rooms.

public class SomeViewModel
{
    private readonly IMeetingRoomService _meetingRoomService;

    public List<MeetingRoom> Rooms { get; set; }

    public SomeViewModel(IMeetingRoomService meetingRoomService)
    {
        this._meetingRoomService = meetingRoomService;
    }

    public void GetRooms()
    {
        // Code that calls the service and sets this.Rooms
        _meetingRoomService.GetRooms(result => Rooms = result);
    }
}

I want to unit test the implementation of SomeViewModel.GetRooms(). (For this question I quickly wrote the implementation but I'm actually trying to use TDD.)

How do I finish this test? I'm using NUnit and Moq.

[Test]
public void GetRooms_ShouldSetRooms()
{
    var theRooms = new List<MeetingRoom>
                       {
                           new MeetingRoom(1, "some room"),
                           new MeetingRoom(2, "some other room"),
                       };

    var meetingRoomService = new Mock<IMeetingRoomService>();

    //How do I setup meetingRoomService so that it gives theRooms in the Action??


    var viewModel = new SomeViewModel(meetingRoomService.Object);

    viewModel.GetRooms();

    Assert.AreEqual(theRooms, viewModel .Rooms);
}

EDIT:

Solution

Read Stephane's answer first.

This is the Test code I ended up writing thanks to stephane's answer:

[Test]
public void GetRooms_ShouldSetRooms()
{
    var meetingRoomService = new Mock<IMeetingRoomService>();
    var shell = new ShellViewModel(meetingRoomService.Object);
    var theRooms = new List<MeetingRoom>
                       {
                           new MeetingRoom(1, "some room"),
                           new MeetingRoom(2, "some other room"),
                       };

    meetingRoomService
        .Setup(service => service.GetRooms(It.IsAny<Action<List<MeetingRoom>>>()))
        .Callback((Action<List<MeetingRoom>> action) => action(theRooms));

    shell.GetRooms();

    Assert.AreEqual(theRooms, shell.Rooms);
}
like image 954
Thomas Stock Avatar asked Feb 20 '11 16:02

Thomas Stock


1 Answers

Here is some pseudo code, I haven't run it. But I think that's what you want.

SetupCallback is what you are interested in.

For all the calls to _meetingRoomServiceFake.GetRooms, simply set the _getRoomsCallback to the parameter passed in.

You now have a reference to the callback that you are passing in your viewmodel, and you can call it with whatever list of MeetingRooms you want to test it. So you can test your asynchronous code almost the same way as synchronous code. it's just a bit more ceremony to setup the fake.

Action<List<MeetingRoom>> _getRoomsCallback = null;
IMeetingRoomService _meetingRoomServiceFake;


private void SetupCallback()
{
     Mock.Get(_meetingRoomServiceFake)
         .Setup(f => f.GetRooms(It.IsAny<Action<List<MeetingRoom>>>()))
         .Callback((Action<List<MeetingRoom>> cb) => _getRoomsCallback= cb);
}

[Setup]
public void Setup()
{
     _meetingRoomServiceFake = Mock.Of<IMeetingRoomService>();
     SetupCallback();
}

[Test]
public void Test()
{

      var viewModel = new SomeViewModel(_meetingRoomServiceFake)

      //in there the mock gets called and sets the _getRoomsCallback field.
      viewModel.GetRooms();
      var theRooms = new List<MeetingRoom>
                   {
                       new MeetingRoom(1, "some room"),
                       new MeetingRoom(2, "some other room"),
                   };

     //this will call whatever was passed as callback in your viewModel.
     _getRoomsCallback(theRooms);
}
like image 55
Stéphane Avatar answered Nov 05 '22 02:11

Stéphane