Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito Capture is not maintaining captured list at time of capturing

In Mockito we have a situation where the capture of a list doesn't return the expected result. Test case:

  1. We add "Pip" to a list
  2. We capture the list
  3. We add "Sok" to the list.

In our assert we only expect "Pip" to be there but "Sok" is there too. We think this is incorrect because at the time of capture "Sok" was not in the list.

java.lang.AssertionError:
Expected :[Pip]
Actual :[Pip, Sok]

  • Has anyone got a solution for this?
  • Is this a bug or a feature in Mockito?
  • Why does Mockito keep a reference to the list and not make a copy of the list a capture time?

Here is the test case:

@RunWith(MockitoJUnitRunner.class)
public class CaptureTest {

    @Captor
    private ArgumentCaptor<List> listCapture;

    @Mock
    private ListPrinter listPrinter;

    private TestClass testClass;

    @Before
    public void setUp() {
        testClass = new TestClass(listPrinter);
    }

    @Test
    public void testCapture() {
        testClass.simulateFailSituation();
        verify(listPrinter).printList(listCapture.capture());
        // THIS FAILS: Expected:[Pip],  Actual:[Pip, Sok]
        assertEquals(Collections.singletonList("Pip"), listCapture.getValue());
    }

    public class TestClass {

        private List list = new ArrayList();
        private ListPrinter listPrinter;

        public TestClass(ListPrinter listPrinter) {
            this.listPrinter = listPrinter;
        }

        private void simulateFailSituation() {
            list.add("Pip");
            listPrinter.printList(list);
            list.add("Sok");
        }
    }

    public interface ListPrinter {
        void printList(List list);
    }
  }
like image 675
Nos Avatar asked Oct 25 '17 14:10

Nos


1 Answers

It might sound like an amazing feature, but then think about it that way: If it was making a copy, where should it stop? You could possibly capture some object that has many references to other objects and you might end up making a deep copy of nearly all the objects in your JVM instance.

That would be a serious performance hit, so I kinda understand why.

So, there are two approaches you could choose from:

  • Use immutable objects where applicable. Other than being easier to test, it also makes the code easier to read and debug.
  • Test the value instantly upon call, rather than capturing the reference. For void methods you could use the doAnswer method with Answer<Void>, test it there or make a copy. By the way this is noting new, see How to make mock to void methods with mockito.

I find it a lot more powerful than verify. In your case, the doAnswer could look like that:

doAnswer(invocation -> {
    assertEquals(Collections.singletonList("Pip"), invocation.getArguments()[0]);
    return null;
}).when(listPrinter).printList(Matchers.anyList());
like image 94
Vlasec Avatar answered Oct 18 '22 13:10

Vlasec