Moq SetupSequence不起作用。

4

我尝试使用Moq 4.5框架的SetupSequence方法。

需要被模拟的类:

public class OutputManager {
    public virtual string WriteMessage(string message) {
    // write a message
    }
}

模拟:

var outputManagerMock = new Mock<OutputManager>();
var writeMessageCalls = 0;
var currentMessage = String.Empty;

outputManagerMock.Setup(o => o.WriteMessage(It.IsAny<string>())).Callback((string m) => {
    writeMessageCalls++;
    message = m;
});

这段代码运行良好。但我希望每次调用WriteMessage方法时都有不同的设置。那么,我会使用SetupSequence而不是Setup

var outputManagerMock = new Mock<OutputManager>();
var writeMessageCalls = 0;
var firstMessage = String.Empty;
var secondMessage = String.Empty;

outputManagerMock.SetupSequence(o => o.WriteMessage(It.IsAny<string>()))
.Callback((string m) => {
    writeMessageCalls++;
    firstMessage = m;
}).Callback((string m) => {
    writeMessageCalls++;
    secondMessage = m;
});

然后我遇到了错误:
错误 CS0411:无法从使用中推断出方法 'SequenceExtensions.SetupSequence(Mock,Expression>)' 的类型参数。请尝试显式指定类型参数。
我在这里找到了可能的解决方案 - Moq中的SetupSequence。但它看起来像是一个变通方法。

展示你实际使用 SetupSequence 的代码。 - Nkosi
期望的行为是什么? - Nkosi
我想为每个 WriteMessage 调用使用不同的 Callback(...)。 我已经在我的问题中添加了一段代码。 根据您的答案,我尝试使用接口而不是特定类。 现在它可以工作了。 我的意思是像这样 'var mock = new Mock<IOutputManager>(); mock.SetupSequence()... ' 但似乎我不能在 SetupSequence 中使用 .Callback() 方法。 只允许使用 .Returns() - Sergey
那是正确的。SetupSequence允许返回结果并抛出异常,它是针对特定用例的设置。任何超出此范围的内容都需要您在问题中提到的解决方法。 - Nkosi
好的,谢谢,我明白了! - Sergey
显示剩余2条评论
2个回答

3

SetupSequence用于根据使用正在设置的方法的尝试次数设置返回序列。下面是基于您的代码的示例,可以说明我所说的:

outputManagerMock.SetupSequence(o => o.WriteMessage(It.IsAny<string>()))
.Returns("Hello for the first attempt!")
.Returns("This is the second attempt to access me!")
.Throws(new Exception());

你是指 outputManagerMock.SetupSequence(...) 吗?不只是 .Setup(...) 吗? - Sergey
好的,当然没问题。抱歉! - Akram Qalalwa

2
如果OutputManager是其他类的依赖项,那么您应该考虑将该类抽象化,以便于在测试中进行模拟。请保留HTML标签。
public interface IOutputManager {
    string WriteMessage(string message);
}

那么,这意味着实现此接口的方式将与您最初的方式相同,并添加了接口。
public class OutputManager : IOutputManager {
    public string WriteMessage(string message) {
        // write a message
    }
}

考虑到您最初尝试模拟OutputManager,那么在这里的假设是它不是被测试系统,因为通常不会模拟测试目标而是其依赖项。

所以让我们假设一个依赖类看起来像这样。

public class DependentOnIOutputManager {
    private IOutputManager outputManager;

    public DependentOnIOutputManager(IOutputManager outputManager) {
        this.outputManager = outputManager;
    }

    public string SomeMethod(string message) {        
        // write a message
        var output = outputManager.WriteMessage(message);
        //...other code
        return output;
    }
}

那么一个示例测试可能如下所示。
[TestMethod]
public void Moq_SetupSequence_Example() {
    //Arrange
    var mock = new Mock<IOutputManager>();

    mock.SetupSequence(x => x.WriteMessage(It.IsAny<string>()))
        .Returns("first")
        .Returns("second")
        .Throws<InvalidOperationException>();

    var outputManager = mock.Object;

    var sut = new DependentOnIOutputManager(outputManager);

    //Act
    var first = sut.SomeMethod("1st");

    var second = sut.SomeMethod("2nd");

    Exception e = null;
    try {
        sut.SomeMethod("3rd");
    } catch (InvalidOperationException ex) {
        e = ex;
    }

    //Assert
    Assert.IsNotNull(first);
    Assert.IsNotNull(second);
    Assert.IsNotNull(e);
}

谢谢,你的IOutputManager接口的想法听起来不错。 - Sergey
很高兴能够帮助。如果这个答案解决了你的问题,你可以将其标记为答案。如果这个答案有用,你也可以投票支持。 - Nkosi
当然!这很有帮助。 - Sergey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接