[英]Mocking method with Action<T> parameter
[unit testing newbie] [c#] [单位测试新手] [c#]
Consider the following scenario: 请考虑以下情形:
I'm using Silverlight and calling a WCF service. 我正在使用Silverlight并调用WCF服务。 Silverlight can only call WCF services asynchronously. Silverlight只能异步调用WCF服务。 I build a wrapper around the WCF service so that I can work with Action parameters. 我构建了一个围绕WCF服务的包装器,以便我可以使用Action参数。 (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. 将GetRooms转换为List<MeetingRoom> GetRooms()
不是一个选项。
I want to use this service in a ViewModel to set a public property called Rooms. 我想在ViewModel中使用此服务来设置名为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(). 我想单元测试SomeViewModel.GetRooms()的实现。 (For this question I quickly wrote the implementation but I'm actually trying to use TDD.) (对于这个问题,我很快写了实现,但实际上我正在尝试使用TDD。)
How do I finish this test? 我该如何完成此测试? I'm using NUnit and Moq. 我正在使用NUnit和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. 首先阅读Stephane的回答。
This is the Test code I ended up writing thanks to stephane's answer: 这是测试代码我最后写的感谢stephane的答案:
[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);
}
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. SetupCallback是你感兴趣的。
For all the calls to _meetingRoomServiceFake.GetRooms, simply set the _getRoomsCallback to the parameter passed in. 对于_meetingRoomServiceFake.GetRooms的所有调用,只需将_getRoomsCallback设置为传入的参数即可。
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. 您现在可以引用要在viewmodel中传递的回调,并且可以使用要测试它的MeetingRoom列表来调用它。 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);
}
You could use an AutoResetEvent to handle async calls. 您可以使用AutoResetEvent来处理异步调用。
Just initialize it as unset and configure your mock service to set it in the callback. 只需将其初始化为unset并配置您的模拟服务以在回调中设置它。 (IE: var mockService = new Mock(); mockService.SetUp(x => x.MyMethod()).Returns(someStuff).Callback(() => handle.Set()); ) (IE:var mockService = new Mock(); mockService.SetUp(x => x.MyMethod())。返回(someStuff).Callback(()=> handle.Set());)
After that I use hadle.WaitOne(1000) to check if it was called. 之后我使用hadle.WaitOne(1000)来检查它是否被调用。 (IMHO 1000 miliseconds are more than enough to run the async code). (恕我直言1000毫秒足以运行异步代码)。
Sorry: This was supposed to go as a reply to the post above... I can't for the life of me figure out how to reply :) 对不起:这应该是作为对上面帖子的回复...我不能为我的生活弄清楚如何回复:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.