Moq第一次和第二次不同的返回值

我有这样的testing:

[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第二次?

使用最新版本的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只会在第三次和第五次尝试成功,否则会抛出exception。

所以对于你的例子,它只是像这样的:

 repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl)) .Returns(null) .Returns(pageModel.Object); 

现有的答案是伟大的,但我想我会抛出我只是使用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); 

添加一个callback没有为我工作,我用这种方法,而不是http://haacked.com/archive/2009/09/29/moq-sequences.aspx ,我结束了这样的testing:

  [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); } 

设置模拟对象时可以使用callback。 看看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(); }); 

现在你可以使用SetupSequence了。 看到这个职位: http : //codecontracts.info/2011/07/28/moq-setupsequence-is-great-for-mocking/

在这里遇到同样的问题,需求略有不同。
我需要从基于不同input值的模拟中获得不同的返回值,并find解决scheme,因为它使用Moq的声明语法(linq to Mocks),所以IMO更具可读性。

 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 

接受的答案 ,以及SetupSequence答案 ,处理返回常量。

Returns()有一些有用的重载,你可以根据发送给模拟方法的参数返回一个值。 根据接受的答案中给出的解决scheme ,这里是另一种重载方法的扩展方法。

 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, }); 

如果需要,可以使用多个参数( T2T3等)为扩展方法创build重载。