【问题标题】:Moq SetupSequence doesn't workMoq SetupSequence 不起作用
【发布时间】:2017-02-04 19:01:28
【问题描述】:

我尝试使用 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<TMock, TResult>(Mock<TMock>, Expression<Func<TMock, TResult>>)' 的类型参数无法从用法中推断出来。
尝试明确指定类型参数。

我在这里找到了可能的解决方案 - SetupSequence in Moq。但这似乎是一种解决方法。

【问题讨论】:

  • 显示你实际使用的代码SetupSequence
  • 另外,OutputManager.WriteMessage 应该是虚拟的,否则 Moq 会抛出异常。
  • 是的,当然是虚拟的。我已经更新了我的代码示例。
  • 期望的行为是什么?
  • 我想为每个WriteMessage 调用使用不同的Callback(...)。我在我的问题中添加了一段代码。根据您的回答,我尝试使用接口而不是特定类。它现在有效。我的意思是这样的 'var mock = new Mock(); mock.SetupSequence()... ' 但看起来我不能将 '.Callback()' 方法与 SetupSequence 一起使用。只允许使用 '.Returns()'。

标签: c# asp.net unit-testing moq


【解决方案1】:

如果OutputManager 是其他类的依赖项,那么您应该考虑对该类进行抽象,以便更容易模拟您的测试。

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 界面的想法听起来不错。
  • 很高兴为您提供帮助。如果此答案解决了您的问题,您可以标记为答案。如果这个答案有用,您也可以投票。
  • 当然!很有帮助。
【解决方案2】:

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(...)?
  • 是的,当然.. 对不起!
猜你喜欢
  • 2019-09-09
  • 1970-01-01
  • 1970-01-01
  • 2022-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多