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

Mar*_*cus 232 c# nunit unit-testing moq

我有这样的测试:

    [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);
    }
Run Code Online (Sandbox Code Playgroud)

GetPageByUrl在我的dashboardpathresolver中运行两次,我如何告诉Moq第一次返回null并且pageModel.Ojbect第二次?

sta*_*low 409

使用最新版本的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);
Run Code Online (Sandbox Code Playgroud)

调用connect只会在第三次和第五次尝试时成功,否则将抛出异常.

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

repository.SetupSequence(x => x.GetPageByUrl<IPageModel>(virtualUrl))
.Returns(null)
.Returns(pageModel.Object);
Run Code Online (Sandbox Code Playgroud)

  • 唉,`SetupSequence()`不适用于`Callback()`.如果只有它,可以用"状态机"方式验证对模拟方法的调用. (6认同)
  • 很好的答案,唯一的限制是"SetupSequence"不适用于受保护的成员. (2认同)
  • 这应该被标记为正确答案。提供的一些选项是有效的,但这个选项很干净,并且以正确的方式使用了 Moq 功能。对于回调,您可以使用 ISetupSequentialResult 接口的“CallBase”。此外,最新版本的 Moq 似乎不再支持 ReturnInOrder 方法。 (2认同)

mo.*_*mo. 108

现有的答案很棒,但我认为我会抛弃我刚才使用的替代方案,System.Collections.Generic.Queue并且不需要任何关于模拟框架的特殊知识 - 因为我写的时候没有任何特殊知识!:)

var pageModel = new Mock<IPageModel>();
IPageModel pageModelNull = null;
var pageModels = new Queue<IPageModel>();
pageModels.Enqueue(pageModelNull);
pageModels.Enqueue(pageModel.Object);
Run Code Online (Sandbox Code Playgroud)

然后...

repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(pageModels.Dequeue);
Run Code Online (Sandbox Code Playgroud)

  • 那是代表.如果代码包含`Dequeue()`而不仅仅是'Dequeue`,那么你就是正确的. (7认同)
  • 您必须为Dequeue使用委托方法.写入样本的方式将始终重复返回队列中的第一个项目,因为在安装时会评估出队. (4认同)
  • 答案是正确的,但请注意,如果您想抛出一个"异常",这将无效,因为您无法"排队"它.但是`SetupSequence`会起作用(例如,请参阅@stackunderflow的回答). (3认同)

Mar*_*cus 30

添加回调对我来说不起作用,我使用这种方法而不是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);
    }
Run Code Online (Sandbox Code Playgroud)


Dan*_*Dan 28

您可以在设置模拟对象时使用回调.请查看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());
Run Code Online (Sandbox Code Playgroud)

您的设置可能如下所示:

var pageObject = pageModel.Object;
repository.Setup(x => x.GetPageByUrl<IPageModel>(path)).Returns(() => pageObject).Callback(() =>
            {
                // assign new value for second call
                pageObject = new PageModel();
            });
Run Code Online (Sandbox Code Playgroud)

  • 确认回调方法不起作用(甚至在早期的Moq版本中尝试过).另一种可能的方法 - 取决于你的测试 - 只是再次执行`Setup()`调用,而`return()`是一个不同的值. (2认同)

ilm*_*tte 19

现在您可以使用SetupSequence.看到这篇文章:http://codecontracts.info/2011/07/28/moq-setupsequence-is-great-for-mocking/

  • 链接已死 (2认同)

Tor*_*lin 6

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

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

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,使用该方法需要指定一些模板参数,但结果仍然具有很好的可读性。

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,
        });
Run Code Online (Sandbox Code Playgroud)

T2如果需要,使用多个参数( 、T3等)为扩展方法创建重载。