Макет услуг в Spring Reactor - PullRequest
       20

Макет услуг в Spring Reactor

0 голосов
/ 11 января 2019

Давайте рассмотрим этот простой метод:

public Mono<SuccessResponse> doSomething(){
        return service1.doSomething()
            .then(service2.doSomething2())
            .thenReturn(new SuccessResponse("Awesome")));
}

Итак, я хочу протестировать этот метод для сценария, в котором service1.doSomething () выдаст ошибку:

when(service1.doSomething()).thenReturn(Mono.error(new IllegalStateException("Something bad happened")));
when(service2.doSomething()).thenReturn(Mono.just(new SomeResponse()))

assertThatThrownBy(() -> testedService.doSomething().block())
            .isExactlyInstanceOf(IllegalStateException.class);

verify(service2, never()).doSomething(); //Why this is executed!?

Мой вопрос: почему service2.doSomething () выполняется один раз? он не должен выполняться, так как service1.doSomething () выдает ошибку выше ...

Ответы [ 2 ]

0 голосов
/ 14 января 2019

Причина, по которой вызывается метод service2.doSomething(), заключается в том, что, хотя Mono может быть ленивым, просто вызов оператора - нет. Вы с нетерпением вызываете методы, которые будут возвращать ленивые Mono s, таким образом собирая конвейер обработки.

Если вы вставите свой код в код, он станет немного понятнее:

    //exception is CREATED immediately, but USED lazily
return Mono.error(new IllegalStateException())
    //mono is CREATED immediately. The data it will emit is also CREATED immediately. But it all triggers LAZILY.
    .then(Mono.just(new SomeResponse()))
    //note that then* operators completely ignore previous step's result (unless it is an error)
    .thenReturn(new SuccessResponse("Awesome"))); 

Некоторые операторы принимают Supplier или Function, что обеспечивает ленивую альтернативу этому нетерпеливому стилю конструирования. Один универсальный способ сделать это - использовать Mono.defer:

public Mono<SuccessResponse> doSomething(){
        return service1.doSomething()
            .then(Mono.defer(service2::doSomething2))
            .thenReturn(new SuccessResponse("Awesome")));
}

Но я бы сказал, что , , если только service2 не скрывает источник, который НЕ является ленивым (например, Mono, адаптированный из CompletableFuture) , проблема не в doSomething, а в тесте .

С помощью макета service2 вы по существу тестируете сборку цепочки операторов, но не в том случае, если этот шаг в конвейере действительно выполняется.

Один из трюков, доступных в reactor-test, - это обернуть Mono.just / Mono.error в PublisherProbe. Это может быть использовано для насмешки над Mono, как вы это сделали, но с добавленной функцией предоставления утверждений о выполнении Mono: была ли она подписана? это было запрошено?

//this is ultimately tested by the assertThrownBy, let's keep it that way:
when(service1.doSomething()).thenReturn(Mono.error(new IllegalStateException("Something bad happened")));

//this will be used to ensure the `service2` Mono is never actually used: 
PublisherProbe<SomeResponse> service2Probe = PublisherProbe.of(Mono.just(new SomeResponse()));
//we still need the mock to return a Mono version of our probe
when(service2.doSomething()).thenReturn(service2Probe.mono());

assertThatThrownBy(() -> testedService.doSomething().block())
            .isExactlyInstanceOf(IllegalStateException.class);

//service2 might have returned a lazy Mono, but it was never actually used:
probe.assertWasNotSubscribed();
0 голосов
/ 11 января 2019

thenReturn не для ошибки броска! Вам нужно использовать thenThrow(), а также вам не нужно писать фиктивный метод для service2, просто проверьте, который вызвал

...