Spring Reactor 中的模拟服务
Mock services in Spring Reactor
让我们来看看这个简单的方法:
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() 在上面抛出错误...
thenReturn
不是为了抛出错误!您需要使用 thenThrow()
并且您不需要为 service2 编写模拟方法,只需验证哪些调用了
调用 service2.doSomething()
方法的原因是,虽然 Mono
可以是惰性的,但显然调用运算符不是。您正在急切地调用将 return 惰性 Mono
的方法,从而组装一个处理管道。
如果你内联你的代码,我认为它会变得更清晰:
//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();
让我们来看看这个简单的方法:
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() 在上面抛出错误...
thenReturn
不是为了抛出错误!您需要使用 thenThrow()
并且您不需要为 service2 编写模拟方法,只需验证哪些调用了
调用 service2.doSomething()
方法的原因是,虽然 Mono
可以是惰性的,但显然调用运算符不是。您正在急切地调用将 return 惰性 Mono
的方法,从而组装一个处理管道。
如果你内联你的代码,我认为它会变得更清晰:
//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();