Search code examples
javaunit-testingtestingmockingencapsulation

Encapsulating and mocking


Suppose I have class with simple dependency:

public interface Dependency {
    int doSomething(int value);
    void doMore(int value);
    int doALotMore(int value);
}

public final class A implements SomeInterface {
    private final Dependency dep;

    public A (Dependency dep) {
        this.dep = dep;
    }

    @Override
    public int add(final int x) {
        dep.doMore(x);
        return x + dep.doSomething(x) + dep.doALotMore(x);
    }
}

And I'm writing test using mocks:

public class TestA {
    private Dependency mockDep;
    private SomeInterface a;

    @Before
    public void setUp() {
        mockDep = Mockito.mock(Dependency.class);
        a = new A(mockDep);
    }

    @Test
    public void shouldAdd() {
        final int x = 5;
        when(mockDep.doSomething(x)).thenReturn(6);
        when(mockDep.doALotMore(x)).thenReturn(7);

        int actual = a.add(x);

        assertThat(actual, is(18));

        verify(mockDep, times(1)).doSomething();
        verify(mockDep, times(1)).doALotMore();
        verify(mockDep, times(1)).doMore();

        verifyNoMoreInteractions(mockDep);
    }
}

So far so good.

So the question is: do we violate encapsulation of class A by verifying how exactly the dependency was used? Does it really needed to test that dependency was used in exactly that way? Shouldn't we test A like a black-box (delete verify invocations from test case and leave just assertThat)? And how to deal with dependencies in such case?

The reason I'm asking is that I caught myself writing good amount of verification dependency code and it seems that we start to test actual internal realization details about class. And I feel uncomfortable about that because when I will try to rewrite this realization details in another way I need to rewrite test cases although the result of add for example will be the same. If I would test my class as a black-box I can change realization details and still be sure that given input will give same output.

Or it is necessary to actually test exactly the realization details and that is the point of unit-test itself? It seems somewhat wrong for me.

Consider this test instead:

 public class TestA {
    private Dependency mockDep;
    private SomeInterface a;
    private final int x = 5;

    @Before
    public void setUp() {
        mockDep = Mockito.mock(Dependency.class);
        a = new A(mockDep);

        when(mockDep.doSomething(x)).thenReturn(6);
        when(mockDep.doALotMore(x)).thenReturn(7);
    }

    @Test
    public void shouldAdd() {
        int actual = a.add(x);

        assertThat(actual, is(18));
    }
}

Solution

  • It really depends on logic which you're testing. Since your example don't provide any context, I'll give you a case when I feel not only comfortable to test such interaction, but even mandatory:

    Let's say you're testing authentication token validation. You pas some token to your validator and it returns true/false. Inside of your validator you're calling some jwt.validate or any other 3rd party hash validation method. In this case I need to know that this validator will be called every time, because I can introduce some if token == null condition inside which will bypass this validation call and just return false. Then your tests could still pass but your code is now vulnerable to timing attack.

    It's one kind of example. The other type of test I'm comfortable of testing that way is so called border testing. I want to know that my class triggers stripe payment gateway - so I mock it and just make sure it gets called without checking anything sophisticated in this particular test.