如何在不使用@WebFluxTest的情况下在Spring WebFlux应用程序的控制器上编写集成测试

时间:2019-04-09 16:31:59

标签: java spring integration-testing spring-webflux

我想在Spring WebFlux应用程序的控制器上编写集成测试,但是我既不想使用@WebFluxTest也不使用@SpringBootTest ...

为什么?

这是因为这些批注启动了整个应用程序,这需要很多秒钟,我只想测试控制器。

我想要做的是像使用MockMvc在经典的Spring Web应用程序上那样编写测试。

我的问题是我不知道如何设置我的View Resolver,所以当我启动测试时出现此错误:

16:56:35.881 [main] ERROR org.springframework.web.server.adapter.HttpWebHandlerAdapter - [22a6d75c] 500 Server Error for HTTP GET "/product"
java.lang.IllegalStateException: Could not resolve view with name '/product/product'.
    at org.springframework.web.reactive.result.view.ViewResolutionResultHandler.lambda$resolveViews$3(ViewResolutionResultHandler.java:276)
    at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:107)
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505)
    at reactor.core.publisher.MonoCollectList$MonoBufferAllSubscriber.onComplete(MonoCollectList.java:118)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:360)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onComplete(FluxConcatMap.java:269)
    at reactor.core.publisher.Operators.complete(Operators.java:131)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:122)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:63)
    at reactor.core.publisher.FluxConcatMap.subscribe(FluxConcatMap.java:121)
    at reactor.core.publisher.MonoCollectList.subscribe(MonoCollectList.java:59)
    at reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59)
    at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150)
    at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505)
    at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:147)
    at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
    at reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)
    at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)
    at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150)
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505)
    at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:241)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73)
    at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:204)
    at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:204)
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505)
    at reactor.core.publisher.MonoIgnoreThen$ThenAcceptInner.onNext(MonoIgnoreThen.java:296)
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144)
    at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505)
    at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:247)
    at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:329)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:192)
    at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92)
    at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)
    at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2070)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:1878)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:1752)
    at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:161)
    at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:53)
    at reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)
    at reactor.core.publisher.MonoDefaultIfEmpty.subscribe(MonoDefaultIfEmpty.java:37)
    at reactor.core.publisher.MonoPeek.subscribe(MonoPeek.java:71)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3695)
    at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:128)
    at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)
    at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
    at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:153)
    at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
    at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74)
    at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74)
    at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150)
    at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)
    at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:275)
    at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:849)
    at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121)
    at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2070)
    at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:162)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:1878)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:1752)
    at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90)
    at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54)
    at reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3695)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:442)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:212)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:139)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:63)
    at reactor.core.publisher.FluxConcatMap.subscribe(FluxConcatMap.java:121)
    at reactor.core.publisher.MonoNext.subscribe(MonoNext.java:40)
    at reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)
    at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)
    at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)
    at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
    at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44)
    at reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61)
    at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3695)
    at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172)
    at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3695)
    at reactor.core.publisher.Mono.subscribeWith(Mono.java:3801)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3689)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3656)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3628)
    at org.springframework.test.web.reactive.server.HttpHandlerConnector.lambda$connect$1(HttpHandlerConnector.java:89)
    at org.springframework.mock.http.client.reactive.MockClientHttpRequest.lambda$null$2(MockClientHttpRequest.java:121)
    at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3695)
    at reactor.core.publisher.FluxConcatIterable$ConcatIterableSubscriber.onComplete(FluxConcatIterable.java:146)
    at reactor.core.publisher.FluxConcatIterable.subscribe(FluxConcatIterable.java:60)
    at reactor.core.publisher.MonoIgnoreElements.subscribe(MonoIgnoreElements.java:37)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3695)
    at reactor.core.publisher.Mono.subscribeWith(Mono.java:3801)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3689)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3656)
    at reactor.core.publisher.Mono.subscribe(Mono.java:3628)
    at org.springframework.test.web.reactive.server.HttpHandlerConnector.connect(HttpHandlerConnector.java:100)
    at org.springframework.test.web.reactive.server.WiretapConnector.connect(WiretapConnector.java:71)
    at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.exchange(ExchangeFunctions.java:103)
    at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.exchange(DefaultWebClient.java:319)
    at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.exchange(DefaultWebTestClient.java:283)
    at fr.myclient.product.controller.ProductControllerIntegrationTest.shouldGetProduct(ProductControllerIntegrationTest.java:21)

当我使用Apache Freemarker时,我试图添加

@Import({ FreeMarkerAutoConfiguration.class})

但是我实际上并没有一个Reactive上下文,因此由于以下原因而没有创建所有必需的bean:

@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)

所以我试图自己创建丢失的豆子:

@Bean
public FreeMarkerViewResolver freeMarkerViewResolver(FreeMarkerProperties properties) {
    FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
    resolver.setPrefix(properties.getPrefix());
    resolver.setSuffix(properties.getSuffix());
    resolver.setRequestContextAttribute(properties.getRequestContextAttribute());
    resolver.setViewNames(properties.getViewNames());
    return resolver;
}

@Bean
public ViewResolverRegistry viewResolverRegistry (ApplicationContext applicationContext, FreeMarkerViewResolver freeMarkerViewResolver) {
    ViewResolverRegistry viewResolverRegistry = new ViewResolverRegistry(applicationContext);
    viewResolverRegistry.viewResolver(freeMarkerViewResolver);
    return viewResolverRegistry;
}

@Bean
public WebFluxConfigurationSupport webFluxConfigurationSupport(ViewResolverRegistry viewResolverRegistry) {
    WebFluxConfigurationSupport webFluxConfigurationSupport = new WebFluxConfigurationSupport();
    ReflectionTestUtils.setField(webFluxConfigurationSupport, "viewResolverRegistry", viewResolverRegistry);
    return webFluxConfigurationSupport;
}

但这不起作用...

好吧,我不知道如何简单地编写此测试并使它保持简单...

控制器:

package fr.myclient.product.controller;

import fr.myclient.product.service.ProductService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import reactor.core.publisher.Mono;

@Slf4j
@Controller
@RequestMapping(value = "/product")
@AllArgsConstructor
public class ProductController {

    private ProductService productService;

    @GetMapping
    public Mono<String> getProduct(Model model, @RequestHeader(name = "productId") String productId) {
        return this.productService.getProduct(productId)
                                         .doOnNext(p -> model.addAttribute("product", p))
                                         .thenReturn("/product/product");
    }
}

集成测试:

package fr.myclient.product.controller;

import fr.myclient.product.config.ControllerTestConfiguration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

@Tag("IntegrationTest")
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ControllerTestConfiguration.class)
public class ProductControllerIntegrationTest {

    @Autowired
    private ProductController productController;

    protected WebTestClient client;

    @BeforeEach
    public void setUp() throws Exception {
        client = WebTestClient.bindToController(productController).build();
    }

    @Test
    public void shouldGetProduct() throws Exception {

        client.get()
              .uri("/product")
              .header("productId", "12345678")
              .accept(MediaType.APPLICATION_JSON_UTF8)
              .exchange()
              .expectStatus()
              .isOk();

    }
}

ControllerTestConfiguration:

package fr.myclient.product.config;

import fr.myclient.product.model.Product;
import fr.myclient.product.service.ProductService;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

import static org.mockito.Mockito.when;

@Configuration
@ComponentScan(basePackages = "fr.myclient.product.controller")
public class ControllerTestConfiguration {
    @Bean
    public ProductService productService() {

        ProductService mockProductService = Mockito.mock(ProductService.class);

        Product data = Product.builder()
                              .code("12345678")
                              .build();

        Mono<Product> monoProduct = Mono.just(data);
        when(mockProductService.getProduct("12345678")).thenReturn(monoProduct);

        return mockProductService;
    }
}

0 个答案:

没有答案