Spring boot Webflux和x27之间的行为不一致;s WebTestClient集成测试和Postman REST调用
我正在努力解决集成测试和普通REST调用之间的行为不一致问题 让我解释一下:我的生产代码中有一个bug,导致: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
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);
}
}