如何使用Moq测试方法调用顺序

时间:2009-11-19 19:02:48

标签: c# unit-testing moq

目前我有:

    [Test]
    public void DrawDrawsAllScreensInTheReverseOrderOfTheStack() {
        // Arrange.
        var screenMockOne = new Mock<IScreen>();
        var screenMockTwo = new Mock<IScreen>();
        var screens = new List<IScreen>();
        screens.Add(screenMockOne.Object);
        screens.Add(screenMockTwo.Object);
        var stackOfScreensMock = new Mock<IScreenStack>();
        stackOfScreensMock.Setup(s => s.ToArray()).Returns(screens.ToArray());
        var screenManager = new ScreenManager(stackOfScreensMock.Object);
        // Act.
        screenManager.Draw(new Mock<GameTime>().Object);
        // Assert.
        screenMockOne.Verify(smo => smo.Draw(It.IsAny<GameTime>()), Times.Once(),
            "Draw was not called on screen mock one");
        screenMockTwo.Verify(smo => smo.Draw(It.IsAny<GameTime>()), Times.Once(), 
            "Draw was not called on screen mock two");
    }

但是我在生产代码中绘制对象的顺序无关紧要。我可以先做一个,或者两个没关系。但是它应该很重要,因为抽奖顺序很重要。

你如何(使用Moq)确保按特定顺序调用方法?

修改

我摆脱了那个考验。 draw方法已从我的单元测试中删除。我只需要手动测试它的工作原理。顺序的颠倒虽然被带入了一个单独的测试类,在那里进行了测试,所以并非一切都不好。

感谢您关注他们正在研究的功能的链接。我当然希望它能很快得到补充,非常方便。

6 个答案:

答案 0 :(得分:31)

我最近创建了Moq.Sequences,它可以检查Moq中的排序。您可能需要阅读描述以下内容的post

  • 支持方法调用,属性 二传手和吸气鬼。
  • 允许您指定的数量 特定电话的时间应该是 预期。
  • 提供允许您进行的循环 小组打电话给经常性小组。
  • 允许您指定编号 应该预期循环次数。
  • 预计会被呼叫的呼叫 按顺序可以互相混合 按任何顺序预期的电话。
  • 多线程支持。

典型用法如下:

[Test]
public void Should_show_each_post_with_most_recent_first_using_sequences()
{
    var olderPost = new Post { DateTime = new DateTime(2010, 1, 1) };
    var newerPost = new Post { DateTime = new DateTime(2010, 1, 2) };
    var posts = new List<Post> { newerPost, olderPost };

    var mockView = new Mock<BlogView>();

    using (Sequence.Create())
    {
        mockView.Setup(v => v.ShowPost(newerPost)).InSequence();
        mockView.Setup(v => v.ShowPost(olderPost)).InSequence();

        new BlogPresenter(mockView.Object).Show(posts);
    }
}

答案 1 :(得分:13)

使用Moq CallBacks的简单解决方案:

    [TestMethod]
    public void CallInOrder()
    {
        // Arrange
        string callOrder = "";

        var service = new Mock<MyService>();
        service.Setup(p=>p.FirstCall()).Returns(0).CallBack(()=>callOrder += "1");
        service.Setup(p=>p.SecondCall()).Returns(0).CallBack(()=>callOrder += "2");

        var sut = new Client(service);

        // Act
        sut.DoStuff();

        // Assert
        Assert.AreEqual("12", callOrder);
    }

答案 2 :(得分:4)

它似乎目前尚未实施。见Issue 24: MockSequenceThis thread讨论了这个问题。

但您可能会考虑修改测试。我一般认为测试顺序导致脆弱的测试,因为它经常测试实现细节。

编辑:我不确定这是否解决了OP的问题。 Lucero的回答可能会更有帮助。

答案 3 :(得分:2)

看看这个blog post,它可以解决您的问题。

答案 4 :(得分:1)

否则你可以使用Callback函数并增加/存储callIndex值。

答案 5 :(得分:0)

从原始帖子我可以假设测试方法执行以下操作调用:

var screenOne = new Screen(...);
var screenTwo = new Screen(...);
var screens = new []{screenOne, screenTwo};
var screenManager = new ScreenManager(screens);
screenManager.Draw();

其中'Draw'方法的实现是这样的:

public class ScreenManager
{
    public void Draw()
    {
        _screens[0].Draw();
        _screens[1].Draw();
    }
}

从我的角度来看,如果调用顺序非常重要,那么应该在系统中引入附加结构(描述序列)。

最简单的实现:每个屏幕都应该知道他的后续元素,并在绘制完成后调用其Draw方法:

// 1st version
public class Screen(Screen screenSubSequent)
{
    private Screen _screenNext;
    public Screen(Screen screenNext)
    {
        _screenNext=screenNext;
    }
    public void Draw()
    {
        // draw himself
        if ( _screenNext!=null ) _screenNext.Draw();
    }
}

public class ScreenManager
{
    public void Draw()
    {
        _screens[0].Draw();
    }
}

static void Main()
{
    var screenOne = new Screen(null, ...);
    var screenTwo = new Screen(screenOne, ...);
    var screens = new []{screenOne, screenTwo};
    var screenManager = new ScreenManager(screens);
}

从一点来看,每个Screen元素应该对另一个元素有所了解。这并不总是好的。如果是这样的话:你可以像'ScreenDrawer'那样创建一些类。这个对象将存储自己的屏幕和后续屏幕(可能从Screen类继承他。使用其他世界:'ScreenDrawer'类描述系统结构。这是最简单的实现方案:

// 2nd version
public class ScreenDrawer
{
    private Screen _screenNext;
    public ScreenDrawer(Screen screenNext, ...) : base (...)
    {
        _screenNext=screenNext;
    }
    public void Draw()
    {
        // draw himself
        if ( _screenNext!=null ) _screenNext.Draw();
    }
}

public class ScreenManager
{
    public void Draw()
    {
        _screens[0].Draw();
    }
}

static void Main()
{
    var screenOne = new ScreenDrawer(null, ...);
    var screenTwo = new ScreenDrawer(screenOne, ...);
    var screens = new []{screenOne, screenTwo};
    var screenManager = new ScreenManager(screens);
}

第二种方法引入了额外的继承,但不需要Screen类来了解他的子序列元素。

总结:两种方法都执行子序列调用,不需要“序列”测试。相反,他们需要测试当前的“屏幕”是否调用另一个并测试'ScreenManager'是否按顺序调用第一个元素的'Draw'方法。

这种方法:

  1. 更可测试(可以使用大多数测试框架实现,而无需支持'序列测试');
  2. 更稳定(没有人可以轻松更改序列:hi不仅需要更新源代码,还需要更新一些测试);
  3. 更多面向对象(您使用的是对象,而不是像'sequence'这样的抽象实体);
  4. 结果:更加可支持。
  5. 感谢。