我有这样的测试:
[TestCase("~/page/myaction")]
public void Page_With_Custom_Action(string path) {
// Arrange
var pathData = new Mock<IPathData>();
var pageModel = new Mock<IPageModel>();
var repository = new Mock<IPageRepository>();
var mapper = new Mock<IControllerMapper>();
var container = new Mock<IContainer>();
container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageModel.Object);
pathData.Setup(x => x.Action).Returns("myaction");
pathData.Setup(x => x.Controller).Returns("page");
var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);
// Act
var data = resolver.ResolvePath(path);
// Assert
Assert.NotNull(data);
Assert.AreEqual("myaction", data.Action);
Assert.AreEqual("page", data.Controller);
}
GetPageByUrl在我的dashboardpathresolver中运行两次,如何告诉Moq第一次返回null并且pageModel.Ojbect第二次?
答案 0 :(得分:358)
使用最新版本的Moq(4.2.1312.1622),您可以使用 SetupSequence 设置一系列事件。这是一个例子:
_mockClient.SetupSequence(m => m.Connect(It.IsAny<String>(), It.IsAny<int>(), It.IsAny<int>()))
.Throws(new SocketException())
.Throws(new SocketException())
.Returns(true)
.Throws(new SocketException())
.Returns(true);
调用connect只会在第三次和第五次尝试时成功,否则将抛出异常。
因此,对于您的示例,它将类似于:
repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl))
.Returns(null)
.Returns(pageModel.Object);
答案 1 :(得分:103)
现有的答案很棒,但我想我会选择使用System.Collections.Generic.Queue
并且不需要任何关于模拟框架的特殊知识 - 因为我写的时候没有它! :)
var pageModel = new Mock<IPageModel>();
IPageModel pageModelNull = null;
var pageModels = new Queue<IPageModel>();
pageModels.Enqueue(pageModelNull);
pageModels.Enqueue(pageModel.Object);
则...
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(pageModels.Dequeue);
答案 2 :(得分:28)
添加回调对我不起作用,我使用这种方法代替http://haacked.com/archive/2009/09/29/moq-sequences.aspx,我最终得到了这样的测试:
[TestCase("~/page/myaction")]
[TestCase("~/page/myaction/")]
public void Page_With_Custom_Action(string virtualUrl) {
// Arrange
var pathData = new Mock<IPathData>();
var pageModel = new Mock<IPageModel>();
var repository = new Mock<IPageRepository>();
var mapper = new Mock<IControllerMapper>();
var container = new Mock<IContainer>();
container.Setup(x => x.GetInstance<IPageRepository>()).Returns(repository.Object);
repository.Setup(x => x.GetPageByUrl<IPageModel>(virtualUrl)).ReturnsInOrder(null, pageModel.Object);
pathData.Setup(x => x.Action).Returns("myaction");
pathData.Setup(x => x.Controller).Returns("page");
var resolver = new DashboardPathResolver(pathData.Object, repository.Object, mapper.Object, container.Object);
// Act
var data = resolver.ResolvePath(virtualUrl);
// Assert
Assert.NotNull(data);
Assert.AreEqual("myaction", data.Action);
Assert.AreEqual("page", data.Controller);
}
答案 3 :(得分:23)
设置模拟对象时可以使用回调。看一下Moq Wiki(http://code.google.com/p/moq/wiki/QuickStart)中的示例。
// returning different values on each invocation
var mock = new Mock<IFoo>();
var calls = 0;
mock.Setup(foo => foo.GetCountThing())
.Returns(() => calls)
.Callback(() => calls++);
// returns 0 on first invocation, 1 on the next, and so on
Console.WriteLine(mock.Object.GetCountThing());
您的设置可能如下所示:
var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
{
// assign new value for second call
pageObject = new PageModel();
});
答案 4 :(得分:19)
现在您可以使用SetupSequence。请参阅此帖子:http://codecontracts.info/2011/07/28/moq-setupsequence-is-great-for-mocking/
答案 5 :(得分:2)
这里遇到的问题与需求略有不同有同样的问题 我需要从基于不同输入值的模拟中获得不同的返回值,并找到IMO更易读的解决方案,因为它使用Moq的声明性语法(linq to Mocks)。
public interface IDataAccess
{
DbValue GetFromDb(int accountId);
}
var dataAccessMock = Mock.Of<IDataAccess>
(da => da.GetFromDb(It.Is<int>(acctId => acctId == 0)) == new Account { AccountStatus = AccountStatus.None }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 1)) == new DbValue { AccountStatus = AccountStatus.InActive }
&& da.GetFromDb(It.Is<int>(acctId => acctId == 2)) == new DbValue { AccountStatus = AccountStatus.Deleted });
var result1 = dataAccessMock.GetFromDb(0); // returns DbValue of "None" AccountStatus
var result2 = dataAccessMock.GetFromDb(1); // returns DbValue of "InActive" AccountStatus
var result3 = dataAccessMock.GetFromDb(2); // returns DbValue of "Deleted" AccountStatus
答案 6 :(得分:2)
accepted answer以及SetupSequence answer处理返回的常量。
Returns()
有一些有用的重载,您可以根据发送到模拟方法的参数返回值。根据接受的答案中给出的the solution,这是另一种超载的扩展方法。
public static class MoqExtensions
{
public static IReturnsResult<TMock> ReturnsInOrder<TMock, TResult, T1>(this ISetup<TMock, TResult> setup, params Func<T1, TResult>[] valueFunctions)
where TMock : class
{
var queue = new Queue<Func<T1, TResult>>(valueFunctions);
return setup.Returns<T1>(arg => queue.Dequeue()(arg));
}
}
不幸的是,使用该方法需要您指定一些模板参数,但结果仍然可读。
repository
.Setup(x => x.GetPageByUrl<IPageModel>(path))
.ReturnsInOrder(new Func<string, IPageModel>[]
{
p => null, // Here, the return value can depend on the path parameter
p => pageModel.Object,
});
如果需要,为具有多个参数(T2
,T3
等)的扩展方法创建重载。
答案 7 :(得分:0)
我们可以简单地将一个变量声明为 int
作为数据类型。将其初始化为 zero
,然后按如下方式增加它的值:
int firstTime = 0;
repository.Setup(_ => _.GetPageByUrl<IPageModel>(path)).Returns(() =>
{
if (firstTime == 0)
{
firstTime = 1;
return null;
}
else if(firstTime == 1)
{
firstTime = 2;
return pageModel.Object;
}
else
{
return null;
}
});