6

I have to test a method which uses a mutable object

private final List<LogMessage> buffer;
...
flushBuffer() {
  sender.send(buffer);
  buffer.clear();
}

I need to test that it sends buffers with exact size.
ArgumentCaptor is not applicable because the captured collection is clear by the time of assertion.

Is there a kind of matcher which can reuse Hamcrest's hasSize() and does check right in time of method call?

I would prefer something like this hypothetical collectionWhich matcher:

bufferedSender.flushBuffer();
verify(sender).send(collectionWhich(hasSize(5)));
Denys Kurochkin
  • 1,360
  • 1
  • 18
  • 34
  • Really interesting and original question ! (+1) I wrote an answer from my own experience and I hope that it will have other answers that could learn me things on this use case. – davidxxx Apr 25 '18 at 15:20

3 Answers3

5

A lightweight alternative to David's idea: Use an Answer to make a copy at the time of the call. Untested code, but this should be pretty close:

final List<LogMessage> capturedList = new ArrayList<>();
// This uses a lambda, but you could also do it with an anonymous inner class:
// new Answer<Void>() {
//   @Override public Void answer(InvocationOnMock invocation) { /* ... */ }
// }
when(sender.send(any())).thenAnswer(invocation -> {
  List<LogMessage> argument = (List<LogMessage>) invocation.getArguments()[0];
  capturedList.addAll(argument);
});
bufferedSender.flushBuffer();
assertThat(capturedList).hasSize(5);
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
3

The Jeff Bowman answer is fine but I think that we can improve it by inlining the assertion in the Answer object itself. It avoids creating unnecessary copy objects and additional local variable(s).

Besides in cases of we need to copy the state of custom objects (by performing a deep copy of it), this way is much simpler. Indeed, it doesn't require any custom code or library to perform the copies as the assertion is done on the fly.

In Java 8, it would give :

import  static org.mockito.Mockito.*;

when(sender.send(any())).thenAnswer(invocation -> {
  List<LogMessage> listAtMockTime = invocation.getArguments()[0];
  Assert.assertEquals(5, listAtMockTime.getSize());
});
bufferedSender.flushBuffer();

Note that InvocationOnMock.getArgument(int index) returns an unbounded wildcard (?). So no cast is required from the caller as the returned type is defined by the target : here the declared variable for which one we assign the result.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
2

You would have the same issue than with ArgumenCaptor as the verify() method checks the invocation with the state of the object after the execution. No capture is performed to keep only the state at the invocation time.
So with a mutable object I think that a better way would be to not use Mockito and instead create a stub of the Sender class where you capture the actual size of the collection as send() is invoked.

Here is a sample stub class (minimal example that you could of course enrich/adapt) :

class SenderStub extends Sender {

    private int bufferSize;
    private boolean isSendInvoked;

    public int getBufferSize() {
        return bufferSize;
    }

   public boolean isSendInvoked(){
      return isSendInvoked;
   }

    @Override
    public void send(List<LogMessage> buffer ) {
        this.isSendInvoked = true;
        this.bufferSize = buffer.size();
    }    
}

Now you have a way to check whether the Sender was invoked and the size (or even more) of that.

And so put aside Mockito to create this mock and verify its behavior :

SenderStub sender = new SenderStub();
MyClassToTest myClass = new MyClassToTest(sender);
// action
myClass.flushBuffer();
// assertion
Assert.assertTrue(sender.isInvoked()); 
Assert.assertEquals(5, sender.getBufferSize());
davidxxx
  • 125,838
  • 23
  • 214
  • 215