Spring boot Webflux和x27之间的行为不一致;s WebTestClient集成测试和Postman REST调用

Spring boot Webflux和x27之间的行为不一致;s WebTestClient集成测试和Postman REST调用,spring-boot,spring-webflux,spring-test,project-reactor,Spring Boot,Spring Webflux,Spring Test,Project Reactor,我正在努力解决集成测试和普通REST调用之间的行为不一致问题 让我解释一下:我的生产代码中有一个bug,导致:NoSuchElementException:Source在我从rest客户端(例如Postman)执行POST时为空异常 我试图重用已经订阅的Mono。见下文: public Mono<ServerResponse> createUser(ServerRequest serverRequest) { Mono<User> userMono = serve

我正在努力解决集成测试和普通REST调用之间的行为不一致问题

让我解释一下:我的生产代码中有一个bug,导致:
NoSuchElementException:Source在我从rest客户端(例如Postman)执行
POST
时为空
异常

我试图重用已经订阅的
Mono
。见下文:

public Mono<ServerResponse> createUser(ServerRequest serverRequest) {
    Mono<User> userMono = serverRequest.bodyToMono(User.class);//Can only be subscribed to once!!
    return validateUser(userMono)
        .switchIfEmpty(validateEmailNotExists(userMono))
        .switchIfEmpty(saveUser(userMono))
        .single();
}
即使我指定了完整的web环境,如下所示:

@SpringBootTest(
    properties = "spring.main.web-application-type=reactive",
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)

当邮递员/curl的POST调用失败时,我不知道为什么我的测试通过了。有人能给点建议吗?区别在哪里?

区别在于如何实例化您的
Mono
。在“real”示例中,您使用的是
serverRequest.bodytomino(User.class)
,它将读取输入流,然后将结果解析为对象。当输入流被消耗和关闭时,其中的数据就消失了——你不能再打开它,然后从中获得与以前相同的数据。因此,除非缓存了
User
对象的结果,否则无法从同一
Mono
对象中获取该对象

Mono.just()
但是,您在该测试中构建的用户值本质上只是存储在该
Mono
中的一个常量,因此它可以无限期地重放而不会出现问题

作为一个简化示例,请注意以下内容:

public class DemoApplication {

    static class Foo {

        String bar;

        public String toString() {
            return bar;
        }

    }

    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.bar = "hello";
        Mono<Foo> mono = Mono.just(foo);

        mono.subscribe(System.out::println);
        mono.subscribe(System.out::println);
    }

}
public class DemoApplication {

    static class Foo {

        String bar;

        public String toString() {
            return bar;
        }

    }

    public static void main(String[] args) {
        InputStream targetStream = new ByteArrayInputStream("{\"bar\":\"hello\"}".getBytes());
        Mono<Foo> mono = Mono.fromSupplier(() -> new Gson().fromJson(new InputStreamReader(targetStream), Foo.class));

        mono.subscribe(System.out::println);
        mono.subscribe(System.out::println);
    }

}

…将只打印一次“hello”,因为第一次调用总是使用流。

感谢您的回复。我理解你的答案,但是没有办法在测试中模仿“真实”的例子吗?我尝试了以下方法:
.body(BodyInserters.fromObject(user))
而不是
.body(Mono.just(user)、user.class)
。但我仍然无法复制测试中的错误。。。更重要的是,不必担心产品缺陷无法被覆盖相同执行路径的集成测试发现或复制?@balteo要查看更新,您必须模仿
InputStream
行为,才能在“脱机”测试中复制此行为。问题在于它的执行路径完全不同,因为Mono的创建方式不同。如果您想在上面的示例中实现这一点,可以创建一个实用方法,该方法接受
用户
对象,将其序列化为JSON,然后基于该JSON创建一个
输入流
,以便在
Mono.fromSupplier
中使用。当然更详细,但这意味着您的测试更接近您的真实示例,因此可能会遇到这样的情况。
public class DemoApplication {

    static class Foo {

        String bar;

        public String toString() {
            return bar;
        }

    }

    public static void main(String[] args) {
        InputStream targetStream = new ByteArrayInputStream("{\"bar\":\"hello\"}".getBytes());
        Mono<Foo> mono = Mono.fromSupplier(() -> new Gson().fromJson(new InputStreamReader(targetStream), Foo.class));

        mono.subscribe(System.out::println);
        mono.subscribe(System.out::println);
    }

}