【问题标题】:How To Test @Controller with Spring Boot 2 and WebFlux如何使用 Spring Boot 2 和 WebFlux 测试 @Controller
【发布时间】:2018-10-21 13:13:37
【问题描述】:

我目前正在尝试在普通控制器中测试一个简单的 post 方法,该方法返回一个 Mono 以重定向到不同的页面,或者在这种情况下是主页。我尝试了各种不同的方法来模拟组件,但我似乎总是在测试中返回一个 null Mono,所有通过表单提交正常工作。

@Controller
public class AddNewEntryController {

private final EntryService service;

@PostMapping("/add-new-entry")
public Mono<String> addNewEntrySubmit(@ModelAttribute("timeEntry") Entry entry) {
    return service.addTimeKeepingEntry(Flux.just(entry)).then(Mono.just("redirect:/"));
   }
}

以及服务类代码

public Mono<Void> addTimeKeepingEntry(Flux<Entry> entry) {
    return entry.flatMap(entry -> Mono.when(repository.save(entry).log("Save to DB"))
            .log("add entry when")).then().log("done");
}

和测试代码

@RunWith(SpringRunner.class)
@WebFluxTest(controllers = AddNewEntryController.class)
@Import({ThymeleafAutoConfiguration.class})
public class AddNewEntryControllerTest {

@Autowired
WebTestClient webTestClient;

@MockBean
EntryService service;

@Test
public void addNewEntrySubmit() {
    MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
    formData.add("month", month);
    formData.add("dateOfMonth", Integer.toString(21));
    formData.add("startTime", "09:00");

    when(service.addEntry(Flux.just(entry1))).thenReturn(Mono.empty());

    webTestClient.post().uri("/add-new-entry").body(BodyInserters.fromFormData(formData)).exchange().expectStatus().isSeeOther().expectHeader().valueEquals(HttpHeaders.LOCATION, "/");

每当我运行测试时,我总是得到一个 Null 指针,并且在调试后它指向 Mono 为 Null。问题是我不确定哪个 Mono 或哪个步骤。

我得到的 StackTrace 如下。

java.lang.NullPointerException: null
at com.dbeer.timekeeping.UI.AddNewEntryController.addNewEntrySubmit(AddNewEntryController.java:47) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:243) ~[spring-webflux-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$invoke$0(InvocableHandlerMethod.java:138) ~[spring-webflux-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118) [reactor-core-3.1.9.RELEASE.jar:3.1.9.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1083) ~[reactor-core-3.1.9.RELEASE.jar:3.1.9.RELEASE]
at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:247) ~[reactor-core-3.1.9.RELEASE.jar:3.1.9.RELEASE]
at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:329) ~[reactor-core-3.1.9.RELEASE.jar:3.1.9.RELEASE]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:185) ~[reactor-core-3.1.9.RELEASE.jar:3.1.9.RELEASE]

【问题讨论】:

  • @AndyWilkinson 是的,请参阅上面的编辑测试类使用 @RunWith(SpringRunner.class) @WebFluxTest(controllers = AddNewEntryController.class) @Import({ThymeleafAutoConfiguration.class}) 注释
  • 您可能需要显示您的完整代码,例如您的控制器具有“EntryService”,但测试具有具有不同签名的“TimeKeepingEntryService”。我运行了你的代码,它在控制器级别上运行良好,所以我怀疑这是你没有显示的百里香叶配置。
  • @KevinHussey 这是一个错字。您可以在gitlab.com/dmbeer/timekeeping 看到该项目

标签: spring-boot spring-webflux


【解决方案1】:

查看您的项目后,问题似乎是控制器命名和 html 页面的一致性。 例如在 header.html 中,您有一个链接 add-entry 的 url,但如果您将 header 中的 url 更改为 add-new-entry,您的控制器有 add-new-entry 它可以工作。

作为清理,您应该使用 thmyeleaf 生成 URL 而不是普通的 href,就像您以后添加安全性一样,thymeleaf 会将会话 ID 添加到 URL 等

***********Edit 拉出分支,可以重现 *******

线

 given(service.addTimeKeepingEntry(Flux.just(new TimeKeepingEntry(month, 21, "Tuesday", "09:00", "30", "17:00", "7.5", false)))).willReturn(Mono.empty());

是问题所在,因为 Mockito 在这里匹配 Object.equals 并且您还没有定义 equals 对您的对象意味着什么。

另一种方法是捕获传递给mock的对象 例如

 @Captor
    private ArgumentCaptor<Flux<TimeKeepingEntry>> captor;

    @Test
    public void addNewEntrySubmit() {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        formData.add("month", month);
        formData.add("dateOfMonth", Integer.toString(21));
        formData.add("day", "Tuesday");
        formData.add("startTime", "09:00");
        formData.add("endTime", "17:00");
        formData.add("breakLength", "30");

        given(service.addTimeKeepingEntry(any())).willReturn(Mono.empty());

        webTestClient.post().uri("/add-new-entry")
                .body(BodyInserters.fromFormData(formData)).exchange().expectStatus().isSeeOther().expectHeader().valueEquals(HttpHeaders.LOCATION, "/");

        verify(service).addTimeKeepingEntry(captor.capture());
        TimeKeepingEntry timeKeepingEntry = captor.getValue().blockFirst();
        assertThat(timeKeepingEntry.getMonth()).isEqualTo(month);
        //and whatever else you want to test
    }

【讨论】:

  • 感谢这改变了这个值,我仍然得到一个空指针。 java.lang.NullPointerException:在 com.dbeer.timekeeping.UI.AddNewEntryController.addNewEntrySubmit(AddNewEntryController.java:47) 处为 null ~[classes/:na]
  • 进行一些登录 - 您的服务返回 null 或您的对象为 null。你的分贝运行正常吗?我的一切正常
  • 您好,我刚刚根据分支 AddNewEntryControllerTest 更新了存储库,模拟对象似乎被忽略了,我得到的唯一 null 是存储库的服务类中。由于模拟语句,它不应该需要这个。我非常感谢这里的帮助。
  • 谢谢凯文,我知道这与 mockito 有关,因为我必须以某种方式使其匹配表单数据。只是一个非常奇怪的错误。
猜你喜欢
  • 2019-06-27
  • 2019-01-20
  • 2015-05-21
  • 2021-07-23
  • 2018-06-24
  • 2020-07-28
  • 2019-10-28
  • 1970-01-01
  • 2015-05-17
相关资源
最近更新 更多