2011-09-02 110 views
150

我有这样一个测试:不同的返回值第一和第二时间起订量

[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运行两次,我怎么能告诉最小起订量返回NULL首次pageModel.Ojbect的第二?

回答

27

添加的回调并没有为我工作,我用这个方法来代替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); 
    } 
20

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

我两次都得到null当我这样做: var pageModel = new Mock (); IPageModel model = null; (())=> { model = pageModel.Object; }); – Marcus

+0

GetPageByUrl在resolver.ResolvePath方法中调用两次吗? – Dan

+0

是它被调用两次 – Marcus

92

现有的答案是伟大的,但我认为我会抛出我的选择,它只是使用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); 
+0

谢谢。我只是修复了排版pageModel.Object的pageModel模拟的错字,现在它甚至应该也会生成! :) –

+3

答案是正确的,但是请注意,如果你想抛出一个'Exception',因为你不能'Enqueue'它不会工作。但'SetupSequence'将起作用(例如,请参阅@stackunderflow的答案)。 – Halvard

+2

您必须为Dequeue使用委托方法。样本写入的方式总是会重复返回队列中的第一个项目,因为在设置时评估出队队列。 –

262

随着起订量的最新版本(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

好的答案,唯一的限制是“SetupSequence”不能与受保护的成员一起使用。 – Chasefornone

+1

唉,'SetupSequence()'不能和'Callback()'一起使用。如果只有它,那么可以用“状态机”方式来验证对模拟方法的调用。 – urig

1

这里达到了同样的问题略有不同的要求。
我需要得到来自总部设在不同的输入模拟值不同的返回值和IMO更具可读性,因为它使用起订量的声明语法(LINQ到嘲笑)找到解决方案。

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 
0

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

创建用于多个参数,如果需要(T2T3等)扩展方法过载。

相关问题