5

During unit testing retry the response of the mock seems cached, or most probably I am doing something wrong.

I am trying to request something, if error happened then retry twice with delay of 1 second.

  public Mono<Object> someMethod(String someParam) {
    return someInjectedService.doSomething(someParam)
        .doOnError(ex -> System.out.println(ex + ": " + System.currentTimeMillis()))
        .retryWhen(Retry.fixedDelay(2, Duration.ofSeconds(1)).filter(ex -> ex instanceof SomeCustomException))
        .doOnSuccess(result -> doSomethingOnSuccess(result));
  }

My test:

  @Test
  void testshouldRequestThrice_whenErrorOccurs() {
    // Given
    String someParam = "testParam";
    when(someInjectedService.doSomething(someParam))
        .thenReturn(Mono.error(new SomeCustomException("SomeCustomException"))) // 1st response
        .thenReturn(Mono.error(new SomeCustomException("SomeCustomException"))) // 2nd response
        .thenReturn(Mono.just("SomeValidResponse")); // 3rd valid response

    // When
    var result = testService.someMethod(someParam).block();

    // Then
    // Initial request, followed by two retries
    verify(someInjectedService, times(3)).doSomething(someParam);
  }

here someInjectedService is a mock. My plan was to return an exception twice, and on third request return valid response. But what I get is:

org.mockito.exceptions.verification.TooFewActualInvocations: someInjectedService.doSomething("testParam");

Wanted 3 times: -> at shouldRequestThrice_whenErrorOccurs(test.java:138)

But was 1 time:

While I do see 3 prints from .doOnError(ex -> System.out.println(ex + ": " + System.currentTimeMillis())) block, I feel like the actual request is sent only once.

Thank you in advance,

Hatik
  • 1,139
  • 1
  • 15
  • 33
  • If I'm not wrong, `someInjectedService.doSomething(...)` will indeed technically be called only once, it's the returned mono that will be retried. – sp00m Jan 19 '22 at 12:08
  • @sp00m hi, any idea on how I can approach this? – Hatik Jan 19 '22 at 12:10
  • 1
    You could `Mono.defer(() -> someInjectedService.doSomething(someParam))` to ensure the method is effectively called again, which should make your test pass. – sp00m Jan 19 '22 at 12:16
  • 1
    @sp00m thank you, solution is so easy, do you mind posting it as an answer? – Hatik Jan 19 '22 at 12:20
  • I faced the same problem, could you please explain, in the example above, where this line should be added? Mono.defer(() -> someInjectedService.doSomething(someParam)) – Denis Jul 11 '22 at 08:42
  • 1
    @Denis Hi, it should be added right after `return`, replace this line `return someInjectedService.doSomething(someParam)` with `return Mono.defer(() -> someInjectedService... Bla bla)` – Hatik Jul 11 '22 at 16:41

1 Answers1

4

someInjectedService.doSomething(...) will indeed technically be called only once.

You could use Mono.defer(() -> someInjectedService.doSomething(someParam)) instead, to ensure the method is effectively called again, which should make your test pass.

sp00m
  • 47,968
  • 31
  • 142
  • 252