如何在XUnit Moq中验证执行顺序?

时间:2014-06-04 00:50:52

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

我有以下课程:

public class Foo
{
    public int Prop1 { get; set; }
    public string Prop2 { get; set; }

    public Stream TestStream;

    public WriteToTestStream()
    { ... }
}

WriteToTestStream写入TestStream

我想写一个单元测试来确保

    在撰写Prop1 之前,已设置
  1. Prop2TestStream
  2. 一旦将某些内容写入TestStream
  3. ,就不应该访问这些属性

    在这种情况下,如何为WriteToTestStream定义模拟? 如何从Mocked函数中操作局部变量(我可以在单元测试中使用局部变量来记住Stream的写入时间/设置属性)?

1 个答案:

答案 0 :(得分:2)

执行此操作的一种方法是保留本地状态变量,然后使用Moq的回调来跟踪状态,您可以在回调中Assert以确保正确的状态。这是一个例子(虽然在NUnit中)。我还假设您确实想要测试一个以正确顺序使用Foo的SUT(我在这里称为S Bar)。

[TestFixture]
public class UnitTest
{
    private bool _prop1Set = false;
    private bool _prop2Set = false;
    private Mock<IFoo> _mockFoo;

    [SetUp]
    public void Setup()
    {
        _mockFoo = new Mock<IFoo>();
        _mockFoo.SetupSet(m => m.Prop1).Callback(i => _prop1Set = true);
        _mockFoo.SetupSet(m => m.Prop2).Callback(s => _prop2Set = true);
        _mockFoo.Setup(m => m.WriteToTestStream())
                .Callback(() => Assert.IsTrue(_prop1Set && _prop2Set));
    }

    [Test]
    public void EnsureInOrder()
    {
        var sut = new Bar(_mockFoo.Object);
        Assert.DoesNotThrow(() => sut.DoSomethingInOrder());
    }

    [Test]
    public void EnsureOutOfOrder()
    {
        var sut = new Bar(_mockFoo.Object);
        Assert.Catch<Exception>(() => sut.DoSomethingOutOfOrder());
    }
}

请注意,为了模拟您的Foo类,需要覆盖方法和属性,例如:所有虚拟或抽象的界面。

以下是这种重构的一个例子:

public interface IFoo
{
    int Prop1 { get; set; }
    string Prop2 { get; set; }
    void WriteToTestStream();
}

public class Foo : IFoo
{
    public int Prop1 { get; set; }
    public string Prop2 { get; set; }
    // Surely this is internal implementation
    private Stream TestStream;
    public void WriteToTestStream() { }
}

public class Bar
{
    private readonly IFoo _foo;
    public Bar(IFoo injectedFoo)
    {
        _foo = injectedFoo;
    }

    public void DoSomethingInOrder()
    {
        _foo.Prop1 = 1;
        _foo.Prop2 = "Baz";
        _foo.WriteToTestStream();
    }

    public void DoSomethingOutOfOrder()
    {
        _foo.WriteToTestStream();
        _foo.Prop2 = "Baz";
        _foo.Prop1 = 1;
    }
}

另一种方法是使用Declan Whelan's MoqSequences extension,但我必须承认我还没有使用它。

修改

看来MockSequenceInSequence)有made it into recent versions of Moq,虽然这可能会强制执行一个非常严格的序列,但似乎也需要MockBehavior.Strict

[TestFixture]
public class UnitTest
{
    private Mock<IFoo> _mockFoo;

    [SetUp]
    public void Setup()
    {
        _mockFoo = new Mock<IFoo>(MockBehavior.Strict);
        var seq = new MockSequence();
        _mockFoo.InSequence(seq).SetupSet(m => m.Prop1 = It.IsAny<int>());
        _mockFoo.InSequence(seq).SetupSet(m => m.Prop2 = It.IsAny<string>());
        _mockFoo.InSequence(seq).Setup(m => m.WriteToTestStream());
    }

    [Test]
    public void EnsureInOrder()
    {
        var sut = new Bar(_mockFoo.Object);
        Assert.DoesNotThrow(() => sut.DoSomethingInOrder());
    }

    [Test]
    public void EnsureOutOfOrder()
    {
        var sut = new Bar(_mockFoo.Object);
        Assert.Catch<MockException>(() => sut.DoSomethingOutOfOrder());
    }
}