【问题标题】:Mock services in Spring ReactorSpring Reactor 中的模拟服务
【发布时间】:2019-06-06 11:29:55
【问题描述】:

我们来看看这个简单的方法:

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() 在上面抛出了一个错误......

【问题讨论】:

    标签: java spring-boot mockito spring-webflux reactor


    【解决方案1】:

    调用service2.doSomething() 方法的原因是,虽然Mono 可以是惰性的,但显然调用运算符不是。您急切地调用将返回惰性Monos 的方法,从而组装处理管道。

    如果你内联你的代码,我认为它会变得更清晰:

        //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"))); 
    

    一些运营商接受SupplierFunction,这为这种急切的构造风格提供了一种懒惰的替代方案。一种通用的方法是使用Mono.defer

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

    但我认为除非service2 隐藏了一个不懒惰的来源(例如,改编自CompletableFutureMono问题不是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();
    

    【讨论】:

    • 谢谢西蒙,这很有趣而且非常有用!
    • 非常有用!谢谢。我浪费了很多时间来调试这个
    【解决方案2】:

    thenReturn 不是用来抛出错误的!您需要使用 thenThrow() 并且您不需要为 service2 编写模拟方法,只需验证哪些调用了

    【讨论】:

    • 它似乎不像在thenThrow() 的非反应性情况下那样工作,这就是我使用thenReturn() 的原因。该测试包含多个测试场景,这就是 service2 被模拟的原因,但同意我可以使用 do verify 否则。
    • Reactor 通过将错误包装为特定的异步信号来工作。仅当您尝试返回阻塞命令式代码(使用.block())时,它才会将其转换回throw
    猜你喜欢
    • 2017-01-10
    • 2018-01-29
    • 1970-01-01
    • 2021-05-02
    • 2018-06-21
    • 2020-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多