I am handling transactions in Spring boot 2.7 manually:
dataHeader = transactionTemplate.execute(status -> {
final var doc = findDoc(document);
return saveReport(doc);
});
private Doc findDoc(DocumentDto document) {
return docRepository.findByDocType(document.getType(), document.getId()), document.getReport()).get(0);
}
I found this answer hot to test such case, so I wrote this:
when(docRepository.findByDocType(any(), any(), any())).thenReturn(List.of());
when(transactionTemplate.execute(any(TransactionCallback.class)))
.then(invocation -> ((TransactionCallback<Doc>) invocation.getArgument(0))
.doInTransaction(any(TransactionStatus.class)));
var log = LoggerWithMemoryFactory.getBufferedLoggerApi(DocService.class);
final var actual = logEvent.getMessage().getStringPayload();
if (!actual.contains(expected)) {
var message = String.format("expected: '%s' in '%s'", expected, actual);
throw new AssertionFailedError(message, expected, actual);
}
AFAIK I am not mixing any() matchers with values (like "X" or 10L). But still my test fails on wrong number of matchers for docRepository.findByDocType
.
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers! 3 matchers expected, 1 recorded
For curiosity, this works well in a debugger when I put a breakpoint before docRepository.findByDocType
.
I found this Mockito ticket, which looks relevant but I did not understand it and I don't know how to apply it to my situation.
Update:
When I taken the code using mocked object out of lambda, then the test works well:
final var doc = docRepository.findByDocType(document.getType(), document.getId()), document.getReport()).get(0);
dataHeader = transactionTemplate.execute(status -> {
return saveReport(doc);
});
Solution:
Finally, it works when written this way:
doAnswer(invocation -> {
TransactionCallback<List<Doc>> callback = invocation.getArgument(0);
return callback.doInTransaction(new SimpleTransactionStatus());
}).when(transactionTemplate).execute(any());
AFAIK I am not mixing any() matchers with values (like "X" or 10L)
No, but you are pushing Matchers onto the Mockito stack outside of a when
call:
when(transactionTemplate.execute(any(TransactionCallback.class)))
.then(invocation -> invocation.<TransactionCallback<Doc>>getArgument(0)
.doInTransaction(any(TransactionStatus.class)));
^^^^^^^^^^^^^^^^^^^^^^^^^^^ you must not use a matcher when calling a method
What do you expect this code to do? .doInTransaction(any(…))
doesn't make any sense (and any(…)
simply does a return null
after the matcher was recorded). Try it out (in a separate test):
assertNull(any(TransactionStatus.class));
You must call the method with a real argument (but if you don't use status
, then null
might work as well).
Also, transactionTemplate
MUST be a Mockito mock object (@Mock
, Mockito.mock()
, @Spy
, or Mockito.spy()
). If it's not a mock, you can not record matchers for it and can not define stubs for its method calls.