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.