我想在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;
}
}