简体   繁体   中英

Is it correct to isolate a unit test at method level and stub internal method calls?

I always have the doubt if unit testing isolation should be done at very fine grain like stubbing internal method calls, look at the following example with Java, JUnit and Mockito.

package com.company.domain;

public class Foo {
    private FooHelper helper; 

    public setHelper(FooHelper helper) {
        this.helper = helper;
    }

    public Double method1(Integer a, Integer b) {
        ... // logic here
        Integer var = method2(a);  // internal call to method2
        ... // more logic here
    }

    protected Integer method2(Integer c) {
        ... // logic here and return an Integer
    }
}

This is the test class:

package com.company.domain;

@RunWith(MockitoJUnitRunner.class)    
public class FooTest {
    @Mock private FooHelper fooHelperMock; // mocking dependencies as usual
    @InjectMocks @Spy Foo foo; // this way we can stub internal calls

    @Test
    public void method1Test() {
        doReturn(2).when(foo).method2(1); //stub the internal method call
        ... // stub fooHelperMock method calls here

        Double result = foo.method1(1, 2);

        assertEquals(new Double(1.54), result);
        verify(foo, times(1)).method2(1);
        ... // verify fooHelperMock method calls here
    }

    @Test
    public void method2Test() {
        ... // stub fooHelper method calls here
        assertEquals(new Integer(5), foo.method2(3));
        ... // verify fooHelperMock method calls here
    }
}

I think this way you are able to really isolate the code under testing at method level even with internal calls. But I cannot find lot of information about this being considered a good practice or not and why.

In general, testing a class's internal function calls makes your tests far too fragile. When unit testing, you only want to see that the class as a whole is doing what it is supposed to do. You are supposed to intentionally ignore the internals.

Ignoring the internals has a lot of useful benefits. Mainly, if you change the methods -- as long as the public interface stays the same -- your tests will all still pass. Yay. This is the type of stability you want your unit tests to have. Fragile unit tests are almost worse than no unit tests. A fragile test has to be changed every time the code changes even if everything is still working. That provides you with nothing but extra work.

There are exceptions though. I would consider mocking out internal function calls in the following cases:

1) The internal call pulls data from somewhere. For example a database or large file. This is often called a 'boundary' test. We don't want to really set up a database or file to test, so instead we just sub out the function. However, if you find yourself doing this, it probably means that your class should be split into two classes because it is doing too many different things. Make yourself a dedicated database class instead.

2) The internal call does heavy processing that takes a lot of time. In that case, it might make sense to sub out the function instead of waiting. However, once again this might be a sign that you're on the wrong track. You should try to find a way to make things more partitioned so no single step takes that long.

In conclusion, I would highly recommend you do not test at the method level. A class should be a distinct unit. If the class is working everything is fine. This approach give you the testing you need to be secure and the flexibility you need to change things.

@raspacorp

Indeed, if you only test one case, someone could hardcode var = 2 and then pass your test case. But it is your job in designing tests cases to cover enough different cases to be reasonably satisfied that it is behaving appropriately.

As far as tests changing because the code has changed, this is not what you want at all. You want your tests to verify that the responses you are getting are correct for all the different types of cases. As long as the responses are correct, you want to be able to change everything without changing the tests at all.

Imagine a large system with thousands of tests verifing that everything is working right. Now imagine that you want to make a HUGE optimization change that will make things go faster, but change how everything is stored and calculated. What you want your tests to allow you to do is only change code (not tests) and have the tests continually confirm that everything is still working. This is what unit tests are for, not catching every possible line change, but rather to verify that all the calculations and behaviors are corrent.

This article from Martin Fowler answers your question.

You're a mockist, as I'm, but there are many people who don't like this approach and prefer the classicist approach (eg Kent Beck).

Roy Osherove says that the goal of a test, when it fails, is to identify the production code causing the problem, and having that in mind is obvious that the more fine grained the better.

For sure being a mockist may be overwhelming for beginners, but once you're used to the mechanisms of a Mocking library the benefits are uncountable while the effort is not much higher than the other approach.

I do not think it is necessary and I would even go further to say you should not do this. You are writing more test code to test less of you application code. In general it is considered good practice to only test public methods, because a unit test should only be concerned whether a method under test satisfies its contract (ie does what it is supposed to do) and not how the method is implemented. If you have a bug in your private method, how will you know?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM