Matching mutable object without ArgumentCaptor
Asked Answered
T

3

7

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)));
Theta answered 25/4, 2018 at 15:2 Comment(1)
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.Pastore
S
6

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);
Siriasis answered 26/4, 2018 at 0:18 Comment(2)
Very interesting idea (+1). Thanks for your contribution :)Pastore
Tried both, but this is more lightweight even with anonymous inner classTheta
P
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.

Pastore answered 29/4, 2018 at 10:16 Comment(1)
good option, but I prefer separation of when statements and assertionsTheta
P
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());
Pastore answered 25/4, 2018 at 15:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.