I want to create a test that checks if some endpoints are properly disabled in a specific app's configuration. As an input for my parameterized test I would like to use MockHttpServletRequestBuilder
as it's the most descriptive way, but then I can't create any reasonable test description:
@TestFactory
Stream<DynamicTest> should_disable_endpoints(@Autowired MockMvc mvc) {
return StreamEx.of(
get("/endpoint1"),
post("/endpoint2")
)
.chain(Streams::stream)
.map(requestBuilder -> dynamicTest(
requestBuilder.toString(), // <-- I cannot create a proper description here as everything is private
() -> mvc.perform(requestBuilder).andExpect(status().isNotFound())
))
.stream();
}
Comment From: sbrannen
I'm not sure if you are aware of the Named
support in JUnit Jupiter.
For example, the following will generate display names GET /endpoint1
and POST /endpoint2
for your dynamic tests.
import org.junit.jupiter.api.Named;
import static org.junit.jupiter.api.Named.named;
// ...
@TestFactory
Stream<DynamicTest> should_disable_endpoints() {
Stream<Named<RequestBuilder>> requestBuilders = Stream.of(
named("GET /endpoint1", get("/endpoint1")),
named("POST /endpoint2", post("/endpoint2")));
return DynamicTest.stream(requestBuilders,
requestBuilder -> mvc.perform(requestBuilder).andExpect(status().isNotFound()));
}
Does that meet your needs?
Comment From: piotrturski
of course i can repeat the endpoint. the point is to avoid the repetition. i should be able to use requestBuilder.getUrlTemplate()
once instead of repeating the url 25 times
Comment From: sbrannen
of course i can repeat the endpoint. the point is to avoid the repetition.
Like I said, I wasn't sure if you were familiar with the Named
support. Many people do not know about that.
In any case, since there is currently no way to access the properties of the builder, you can avoid duplication by introducing helper methods like this:
@TestFactory
Stream<DynamicTest> should_disable_endpoints() {
Stream<Named<RequestBuilder>> requestBuilders = Stream.of(
GET("/endpoint1"),
POST("/endpoint2"));
return DynamicTest.stream(requestBuilders,
requestBuilder -> mockMvc.perform(requestBuilder).andExpect(status().isNotFound()));
}
static Named<RequestBuilder> GET(String urlTemplate, Object... uriVariables) {
return named("GET " + urlTemplate, get(urlTemplate));
}
static Named<RequestBuilder> POST(String urlTemplate, Object... uriVariables) {
return named("POST " + urlTemplate, post(urlTemplate));
}
When I discover something missing from an API, I often introduce helper methods to work around the issue. So the above might serve you well in lieu of accessor methods in the builder.
Comment From: piotrturski
in this specific case, yes, i can add one helper method for each http method. but MockHttpServletRequestBuilder has plenty of methods that affect the internal state of the request: headers, parameters, queryParams, cookies etc. Should i add helper methods for each combination of those? it would be easier to get them using reflection but the best design seems to be simply to have public accessors
Comment From: sbrannen
Should i add helper methods for each combination of those?
I was not suggesting that as the ideal solution.
Rather, I was pointing out that reasonable workarounds exist based on the status quo of the published APIs.
it would be easier to get them using reflection but the best design seems to be simply to have public accessors
I'm not yet convinced that introducing accessor methods for all properties of MockHttpServletRequestBuilder
is warranted.
Instead, I think implementing a reasonable toString()
in MockHttpServletRequestBuilder
might go a long way without increasing the surface area of the builder API.
For example, something analogous to the following might work well.
https://github.com/spring-projects/spring-framework/blob/2b6f3caff4f3f9eb784c583c5521987fe6c66ceb/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java#L142-L149
Comment From: sbrannen
I pushed a PoC for implementing toString()
in MockHttpServletRequestBuilder
here.
@rstoyanchev, what do you think about introducing toString()
in MockHttpServletRequestBuilder
?
Comment From: simonbasle
@sbrannen I like that toString approach, it makes sense and avoids opening too much of the builder's innards 👍
Comment From: piotrturski
this seems pretty arbitrary and very specific. what if someone is testing headers, parameters, queryParams, cookies, schema etc? fixed arbitrary toString
solves one person's problem. adding getters lets everyone solve their problems
Comment From: rstoyanchev
Indeed, the need here is very specific, having brevity in mind vs what a toString()
would normally do. We could provide a more specifically named method, but generally we don't usually provide getters on a builder, and even less likely to do so in a chained API like MockMvc
.
I suggest building the request to get what you need:
@TestFactory
Stream<DynamicTest> should_disable_endpoints(@Autowired MockMvc mvc) {
return StreamEx.of(
get("/endpoint1"),
post("/endpoint2")
)
.chain(Streams::stream)
.map(requestBuilder -> dynamicTest(
requestBuilder.buildRequest(new MockServletContext()).getRequestURI(),
() -> mvc.perform(requestBuilder).andExpect(status().isNotFound())
))
.stream();
}
Comment From: piotrturski
ugly but actually that solves my problem. i didn't know i can just create new MockServletContext()
and use it to get the request. thank you!
out of curiosity? why u don't expose stuff from builders?
Comment From: rstoyanchev
Not pretty, but suitable for such a less common occasion.
why u don't expose stuff from builders?
To keep the public API focused so you can see what's relevant with code completion.