Closed kdvolder closed 5 years ago
Note: I don't seem to be the only one strugling with this. See: https://stackoverflow.com/questions/35499655/how-to-test-server-sent-events-with-spring
There are two answers there, neither of which seem very satisfactory.
Thanks Kris. Indeed WebTestClient
is the best answer we have for this. Building something (async, streaming parsing) in MockMvc would be non-trivial. We certainly can and need to explain the available options in the documentation however.
How can we use WebTestClient with Spring Boot MVC (starter-web)? It throws unsatisfied dependency on me. WebTestClient is in reactive namespaced package. So we cannot use it for regular MVC? (I tried @AutoConfigureWebTestClient, but that didn't help; even the doc for that annotations says it's only for WebFlux.)
So if WebTestClient is for WebFlux only, and MockMvc is for MVC only, and we want to test Flux/Mono responses, what should we do? I'm trying to find an answer to this for multiple hours at this point... and each of such answers is contradictory or confusing or incorrect, because things have changed.
@daliborfilus, indeed WebTestClient
uses WebClient
from spring-webflux internally but this is a fully expected supported scenario in Spring Boot, just like it is fully supported to use the WebClient
in a Spring Boot application with WebMvc. These are the rules Boot uses to determine the web environment.
In addition in 5.3 we updated WebTestClient
to support both WebFlux and WebMvc controllers so it can be used for both. You might want to review the updated reference in the WebTestClient and the MockMvc sections.
That said, for testing SSE and streaming responses, full end-to-end tests through the WebTestClient
along with the StepVerifier
from Project Reactor is your best bet.
@rstoyanchev Thank you. Seem like I tried to use WebTestClient as autowired component, which doesn't work in this case? (I'm using Spring Boot 2.4.3, which uses Spring Web 5.3.4.)
The WebTestClient link you mention links to sample project https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/client/samples which doesn't use WebTestClient, at least not directly? One of the tests mocks the server side completely, which I don't want - I just want to mock the servlet part and still do end-to-end integration test. (Or even expose random port and test it using rest template - which is maybe what this sample project does? But that's another topic.)
The part 3.6.1 - Setup explains how to create instance of WebTestClient - or more precisely MockMvcWebTestClient - bound to a controller or the application context. I don't want to instantiate the controller here, because then I would need to pass all autowired dependencies to it, so the ApplicationContext is preferrable I think.
So following this, I now have this code:
@ActiveProfiles("test")
// tried with RANDOM_PORT and MOCK
@SpringBootTest(classes = [BackendApplication::class], webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc(printOnlyOnFailure = false)
@AutoConfigureRestDocs(outputDir = "build/generated-snippets")
internal class MonitoringControllerTest {
@Autowired
lateinit var context: WebApplicationContext
lateinit var client: WebTestClient
@BeforeEach
fun setUp() {
client = WebTestClient.bindToApplicationContext(context).build()
}
@Test
fun indexAcceptJsonAndDocument() {
client.get().uri("$contextPath/entities/export")
// .with(SecurityMockMvcRequestPostProcessors.user("user").authorities(allAuthorities())) // FIXME: find replacement for this
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk
.expectBody()
.jsonPath("$.totalElements").isEqualTo(3)
.jsonPath("$.totalPages").isEqualTo(1)
// .andDo(MockMvcRestDocumentation.document("monitored-entities-export")) // FIXME: find replacement for this to get restdocs working; use consumeWith?
}
}
which throws and I don't know why:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'webHandler' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:863)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1344)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1159)
at org.springframework.web.server.adapter.WebHttpHandlerBuilder.applicationContext(WebHttpHandlerBuilder.java:165)
at org.springframework.test.web.reactive.server.ApplicationContextSpec.initHttpHandlerBuilder(ApplicationContextSpec.java:43)
at org.springframework.test.web.reactive.server.AbstractMockServerSpec.configureClient(AbstractMockServerSpec.java:86)
at org.springframework.test.web.reactive.server.AbstractMockServerSpec.build(AbstractMockServerSpec.java:107)
at app.web.data.monitoring.MonitoringControllerTest.setUp(MonitoringControllerTest.kt:35)
Some relevant links: https://stackoverflow.com/questions/60792015/springboot-test-fails-with-no-bean-named-webhandler-available
Tried adding @AutoConfigureWebTestClient - didn't help. Tried adding dependency on starter-webflux along with AutoConfigureWebTestClient - didn't help.
@rstoyanchev Uh. I tried to at least solve the todos in the comments above (mock user, apply rest docs) and I found out that I cannot use .apply(springSecurity())
in the test because of #20606 - kotlin cannot infer the type T.
I would like to supply the the type to the apply method, but I cannot, because the type is package-private - and I can see you are the author of that class (spring-test-5.3.4-sources.jar!/org/springframework/test/web/reactive/server/ApplicationContextSpec.java).
So even if I could get that "webHandler" dependency error go away, I still cannot use the WebTestClient in Kotlin.
As you already found out, you need to use MockMvcWebTestClient
. That has a method to bind to an ApplicationContext
as well so change to the following:
MockMvcWebTestClient.bindToApplicationContext(this.wac).build();
As for #20606 yes that is an issue. I believe there is some effort to resolve it but I don't have the details.
@rstoyanchev Thank you, I will stick to MVC + MockMvc for now and we'll see in the future.
No problem. For streaming and Server-Sent Events, you'll need the WebTestClient
which can also do full end-to-end integration tests but apart from that you can also stick to MockMvc.
I'm strugling with writing tests for rest endpoints that return Flux. I'd like to use mockMvc for this. Is this even possible?
Note that, allthough I have some rest endpoints returning Flux and/or Mono I am not using WebFllux but just regular MVC. The framework handles those types fine and for my Flux example, these are received on the browser side as a 'stream' of ServerSentEvents. This all works fine.
The trouble is. I can't seem to find a good way to write tests agains these endpoints using mockmvc.
The best I came up with is here:
https://github.com/kdvolder/mvc-flux-testing/blob/master/src/test/java/com/example/demo/MvcFluxDemoApplicationTests.java
Is turning the stream of events into String via `getResponse().getContentAsString()' really the best I can do here? (Note: @rstoyanchev has told me it is, mockMvc doesn't provide good ways to test this sort of thing. He recommends to use WebTestClient).
So I submit for consideration the following:
It may be useful to expand mockMvc apis to handle endpoints returning streamed ServerSentEvents better. Perhaps, something that can get the content as a Flux? Then we can use reactive testing support such as StepVerifier to validate the content in our tests.
Add a pointer in the docs on using WebTestClient instead for these scenarios.