开发者

How to mock a method which also belongs to the target class itself?

开发者 https://www.devze.com 2023-01-19 18:18 出处:网络
Let\'s say we are testing a class C which has 2 methods M1 and M2 where M1 calls M2 when executed. Testing M2 is ok, but how can we test M1? The difficulty is that we need to mock M2 if I\'m not misu

Let's say we are testing a class C which has 2 methods M1 and M2 where M1 calls M2 when executed.

Testing M2 is ok, but how can we test M1? The difficulty is that we need to mock M2 if I'm not misunderstanding things.

If so, how can we mock another m开发者_Python百科ethod while testing a method defined in the same class?

[Edit]

Class C has no base classes.


You can do this by setting the CallBase property on the mock to true.

For example, if I have this class:

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

I can setup MethodA and call 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);
}


You should let the call to M1 pass through to a real instance of the M2 method.

In general, you should be testing the black box behaviour of your classes. Your tests shouldn't care that M1 happens to call M2 - this is an implementation detail.

This isn't the same as mocking external dependencies (which you should do)...


For example, say I have a class like this:

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;
}

I want my tests to show that:

  1. Calling Merge(first, second, strategy) results in two accounts getting saved that have been merged using the supplied rule.

  2. Calling Merge(first, second) results in two accounts getting saved that have been merged using the default rule.

Note that both of these requirements are phrased in terms of inputs and effects - in particular, I don't care how the class achieves these results, as long as it does.

The fact that the second method happens to use the first isn't something I care about, or even that I want to enforce - if I do so, I'll write very brittle tests. (There's even an argument that if you've messed about with the object under test using a mocking framework, you're not even testing the original object any more, so what are you testing?) This is an internal dependency that could quite happily change without breaking the requirements:

    // ...
    // 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);
    }

    // ...

As long as my tests only check inputs and effects, I can easily make this kind of refactoring - my tests will even help me to do so, as they make sure I haven't broken the class while making changes.

On the other hand, I do about the AccountMerger using the IAccountDao to save accounts following a merge (although the AccountMerger shouldn't care about the implementation of the DAO, only that it has a Save() method.) This DAO is a prime candidate for mocking - an external dependency of the AccountMerger class, feeling an effect I want to check for certain inputs.


You shouldn't mock methods in the target class, you should only mock external dependencies.

If it seems to make sense to mock M2 while testing M1 it often means that your class is doing too many things. Consider splitting the class and keeping M2 in one class and move M1 to a higher level class, which would use the class containing M2. Then mocking M2 is easy, and your code will actually become better.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号