SalicBlu3
SalicBlu3

Reputation: 1914

Verifying objects within a mocked object are passed into argument in Mockito

Question Context

I have a wrapper class with two objects that contain a list. (i.e. Class1 and Class2 both have a Widget List.)

public class WrapperClass {
    Class1 class1;
    Class2 class2;
}

I have a utils class which processes the Wrapper class

public class WrapperUtils {

    public void processClasses() {
        WrapperClass wrapperClass = getWrapperClass();
        doSomething(class1.getWidgetList());
        doSomething(class2.getWidgetList());
    }

    private WrapperClass getWrapperClass() {
        return wrapperClassFromOnlineService;
    }

    public void doSomething(List<Widget> widgetList) {}
}

Goal

I'd like to use Mockito to verify that the doSomething method gets called with class1's widget list.

My Attempt

@Test
public void main(String[] args) {
    WrapperClass wrapperClass = new WrapperClass();
    wrapperClass.class1 = new Class1();
    wrapperClass.class2 = new Class2();

    WrapperUtils utils = new WrapperUtils();
    Mockito.when(utils.getWrapperClass()).thenReturn(wrapperClass);

    Mockito.verify(utils, times(1)).doSomething(wrapperClass.class1.getWidgetList());
    Mockito.verify(utils, times(1)).doSomething(wrapperClass.class2.getWidgetList());
}

Results of above code: doSomething registers as being called twice for both verify statements. My guess is that the widget lists are treated as the same?

Upvotes: 0

Views: 396

Answers (1)

bric3
bric3

Reputation: 42223

This code has a design issue that makes it difficult to test with mocks.

  1. The test does not expose clearly which object is being tested, and which interaction.
  2. This test is testing the implementation not behavior, that should be the opposite.
  3. Because of problem #1, mockito is misused

What that mean is that

  1. The test should show the case/scenario being tested, clear separations of the fixture, the invocation being tested, and the assertion/verification.
  2. The test should test behavior, that is interactions between the tested objects and collaborators, not internal (as it may change without breaking the test). Also the test can test an expected output given an input.
  3. If #1 and #2 are addressed then it is obvious which type has to be mocked, and to follow tutorials here and there.

Here's an idea of how I'd write the test, this code is mainly focused on interactions, but it is possible to focus the assertions on the state of Class (don't mock them in this case !!!) :

@RunWith(MockitoJUnitRunner.class)
public class WrapperUtilsTest {
    @Mock Class class1;
    @Mock Class class2;

    @Test public void ensure_that____whatever() {
        // given
        WrapperUtils tested_utils = new WrapperUtils(new WrapperClass(class1, class2));

        // when
        tested_utils.processClass();

        // then
        verify(class1).someInteraction();
        verify(class2).someInteraction();
    }
}

And the implementation could look like

public class WrapperUtils {
    private WrapperClass wrapperClass;
    public WrapperUtils(WrapperClass wrapperClass) {
        this.wrapperClass = wrapperClass;
    }

    public void processClasses() {
        doSomething(wrapperClass.class1);
        doSomething(wrapperClass.class2);
    }

    public void doSomething(Class clazz) {
        clazz.someInteraction();
    }
}

Note the wrapperClass is injected in the WrapperUtils via the constructor, that works but you can also pass a Supplier (available in guava or in JDK8), this supplier could get the data from anywhere, a webservice for exemple. Or it could your type. In the test the supplier would be a mock.

@RunWith(MockitoJUnitRunner.class)
public class WrapperUtilsTest {
    @Mock Class class1;
    @Mock Class class2;
    @Mock Supplier<WrapperClass> wrapped_class_supplier;

    @Test public void ensure_that____whatever() {
        // given
        BDDMockito.given(wrapped_class_supplier.get()).willReturn(new WrapperClass(class1, class2));
        WrapperUtils tested_utils = new WrapperUtils(wrapped_class_supplier);

        // when
        tested_utils.processClass();

        // then
        verify(class1).someInteraction();
        verify(class2).someInteraction();
    }
}

I strongly advice you to follow Test Driven Development methodology it really helps to write good software. Also there's this book Growing Object Oriented Software Guided by Tests that is a great read, the book may seem old but it is still describes best practices.

Upvotes: 1

Related Questions