In my project I have an integration test targeted to verify behavior when multiple messages are sent to external system and some of the succeed and others fail. Currently it's implemented with @MockBean:
class SendMultipleMessagesIntgTest extends TestBase {
@MockBean
private ClientService clientService;
@BeforeEach
public void setUp() {
when(clientService.sendMessage(any(MessageRequestDto.class)))
.thenReturn(new MessageResponseDto()
.setFile(new FileDataDto().setId("test-file-id"))
.setSwiftId("test-swift-id-" + System.currentTimeMillis())
);
}
//...
}
In order to be able to call the real method from a test method of the same test class and to reuse Spring application context I'm rewriting the code above to use @SpyBean inherited from TestBase:
@SpringBootTest
abstract class TestBase {
@SpyBean
protected ExternalMessagingClientService externalMessagingClientService;
//...
}
class SendMultipleMessagesIntgTest extends TestBase {
@BeforeEach
public void setUp() {
doReturn(new MessageResponseDto()
.setFile(new FileDataDto().setId("test-file-id"))
.setSwiftId("test-swift-id-" + System.currentTimeMillis())
).when(clientService).sendMessage(any(MessageRequestDto.class));
}
//...
}
When I rely on the stubbing declared in setUp() method everything is fine. The following stubbing also works fine after migration to @SpyBean:
when(clientService.sendMessage(any(MessageRequestDto.class)))
.thenReturn(new MessageResponseDto()
.setFile(new FileDataDto().setId("test-file-id-1")))
.thenReturn(new MessageResponseDto()
.setFile(new FileDataDto().setId("test-file-id-2")));
-->
doReturn(new MessageResponseDto().setFile(new FileDataDto().setId("test-file-id-1")),
new MessageResponseDto().setFile(new FileDataDto().setId("test-file-id-3")))
.when(clientService).sendMessage(any(MessageRequestDto.class));
But if there's an exception thrown between two return answers, the stubbing is broken and exception in fact is not thrown at all:
when(clientService.sendMessage(any(MessageRequestDto.class)))
.thenReturn(new MessageResponseDto().setFile(new FileDataDto().setId("test-file-id-1")))
.thenThrow(new RuntimeException("External messaging service failed"))
.thenReturn(new MessageResponseDto().setFile(new FileDataDto().setId("test-file-id-3")));
When I use mock and the test calls it 3 times, the mock returns, throws, returns. When I use spy it returns 3 times. This is observed only for cases when there's doThrow() between two doReturn().
I've tried to rewrite this template in two ways:
1)
doReturn(new MessageResponseDto()
.setFile(new FileDataDto().setId("test-file-id-1")))
.doThrow(new RuntimeException("External messaging service failed"))
.doReturn(new MessageResponseDto().setFile(new FileDataDto().setId("test-file-id-3")))
.when(externalMessagingClientService).sendMessage(any(MessageRequestDto.class));
doAnswer((new Answer() {
int call;
@Override
public MessageResponseDto answer(InvocationOnMock invocation) {
if (call == 0) {
call++;
return new MessageResponseDto().setFile(new FileDataDto().setId("test-file-id-1"));
}
if (call == 1) {
call++;
throw new RuntimeException("External messaging service failed");
}
return new MessageResponseDto().setFile(new FileDataDto().setId("test-file-id-3"));
}
})).when(clientService).sendMessage(any(MessageRequestDto.class));
Calling Mockito.reset() before stubbing doesn't help.
ClientService is a plain Spring bean:
@Service
@RequiredArgsConstructor
public class ClientService implements IExternalMessagingClientService {
private final RestTemplate restTemplate;
private final ExternalMessagingServiceProperties clientProperties;
private final WebClient webClient;
@Override
@SneakyThrows
@Retryable
public MessageResponseDto sendMessage(MessageRequestDto request) {/*...*/}
What am I doing wrong?
Java8
Spring Boot Starter Parent 2.5,1
Jupiter 5.7.2
Mockito-inline 3.8.0
Mockito-core 3.9.0