如何模拟一个也属于目标类本身的方法?

时间:2010-10-11 15:13:02

标签: unit-testing mocking moq

假设我们正在测试一个C类,它有两个方法M1和M2,其中M1在执行时调用M2。

测试M2是好的,但我们如何测试M1?困难在于,如果我没有误解,我们需要模拟M2。

如果是这样,我们如何在测试同一类中定义的方法时模拟另一种方法?

[编辑]
C类没有基类。

3 个答案:

答案 0 :(得分:7)

您可以通过将模拟的CallBase属性设置为true来完成此操作。

例如,如果我有这个类:

public class Foo
{
    public virtual string MethodA()
    {
        return "A";
    }
    public virtual string MethodB()
    {
        return MethodA() + "B";
    }
}

我可以设置MethodA并调用MethodB:

[Fact]
public void RunTest()
{
    Mock<Foo> mockFoo = new Mock<Foo>();
    mockFoo.Setup(x => x.MethodA()).Returns("Mock");
    mockFoo.CallBase = true;

    string result = mockFoo.Object.MethodB();

    Assert.Equal("MockB", result);
}

答案 1 :(得分:6)

您应该将对M1的调用传递给M2方法的实际实例。

通常,您应该测试类的黑盒行为。您的测试不应该关注M1恰好调用M2 - 这是一个实现细节。

这与模拟外部依赖项(您应该这样做)不同......


例如,假设我有这样一个类:

class AccountMerger
{
    public AccountMerger(IAccountDao dao) 
    {
        this.dao = dao;
    }     

    public void Merge(Account first, Account second, MergeStrategy strategy) 
    {
        // merge logic goes here...
        // [...]
        dao.Save(first);
        dao.Save(second);
    }

    public void Merge(Account first, Account second) 
    {
        Merge(first, second, MergeStrategy.Default);
    }

    private readonly IAccountDao dao;
}

我希望我的测试表明:

  1. 调用Merge(first, second, strategy)会导致保存两个已使用提供的规则合并的帐户。

  2. 调用Merge(first, second)会导致保存已使用默认规则合并的两个帐户。

  3. 请注意,这两个要求都是根据输入效果来表达的 - 特别是我并不关心课程如何实现这些结果,只要它

    第二种方法碰巧使用第一种方法并不是我关心的事情,甚至我想强制实施 - 如果我这样做,我会写非常脆弱的测试。 (甚至有一种观点认为,如果你使用模拟框架搞乱了测试中的对象,你甚至不再测试原始对象,那么 你测试的是什么?)这是一个内部依赖关系,在不违反要求的情况下可以很愉快地改变:

        // ...
        // refactored AccountMerger methods
        // these still work, and still fulfil the two requirements above
    
        public void Merge(Account first, Account second, MergeStrategy strategy) 
        {
            MergeAndSave(first, second, strategy ?? MergeStrategy.Default);
        }
    
        public void Merge(Account first, Account second) 
        {
            // note this no longer calls the other Merge() method
            MergeAndSave(first, second, MergeStrategy.Default);
        }
    
        private void MergeAndSave(Account first, Account second, MergeStrategy strategy) 
        {
            // merge logic goes here...
            // [...]
            dao.Save(first);
            dao.Save(second);
        }
    
        // ...
    

    只要我的测试只检查输入和效果,我就可以轻松地进行这种重构 - 我的测试甚至可以帮助我这样做,因为他们确保我在进行更改时没有打破课程。

    另一方面,我使用AccountMergerIAccountDao在合并后保存帐户(尽管AccountMerger不应该关心DAO的实现,只是它有Save()方法。)这个DAO是模拟的主要候选者 - AccountMerger类的外部依赖,感觉效果我想检查某些输入

答案 2 :(得分:4)

您不应该在目标类中模拟方法,您应该只模拟外部依赖项。

如果在测试M1时模拟M2似乎有意义,那通常意味着你的班级做了太多事情。考虑拆分类并将M2保持在一个类中并将M1移动到更高级别的类,这将使用包含M2的类。然后嘲笑M2很容易,你的代码实际上会变得更好。