Skip to content

Common API for MockMvc tests and for real HTTP tests #19647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Jan 2, 2017 · 17 comments
Closed

Common API for MockMvc tests and for real HTTP tests #19647

spring-projects-issues opened this issue Jan 2, 2017 · 17 comments
Assignees
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Jan 2, 2017

Neil S opened SPR-15081 and commented

There seems to be no common API which can be configure to do mock HTTP tests as well as real HTTP tests.

MockMvc tests suffice during development but for production deployment projects might like to do a complete end-to-end integration test by run real tests against a the (maybe embedded) container sever which will be used in production.

At the moment that means writing the same tests twice using the MockMvc and then TestRestTemplate+assert APIs.

IMHO MockMvc has the friendlier API which results in concise and easier to read test code.


Affects: 4.3.5

Issue Links:

0 votes, 6 watchers

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

I see where you're coming from but for starters a name such as MockMvc would be a misnomer. More importantly by design it is a server-side test framework that simulates running in a Servlet container with the Spring MVC DispatcherServlet and that makes it possible to provide unique value such as whitebox testing -- e.g. assert things like model attributes, handled exceptions, the handler used, and others that couldn't be verified otherwise, and it also allows creating tests that are closer to unit testing such as testing a single controller at a time.

For an integration test with actual HTTP requests the client and server are much more decoupled and there is little unique value I can see us providing over existing tools such as REST Assured.

The general idea is that MockMvc allows you to test thoroughly your Spring MVC controllers and web framework configuration without running a server. Then you should need a lot less full integration testing.

@spring-projects-issues
Copy link
Collaborator Author

Neil S commented

Thanks, all good points.

I do think we ought to have some API which can test both server side and full end to end. Maybe that shouldn't be MockMvc... although I prefer its API over assert and feel that pushing everyone to TestRestTemplate would be a somewhat backward step.

How about a (generically named, say 'MvcTest'?) interface that MockMvc implements, which also has an implementation that makes real HTTP requests?

You're right that this wouldn't provide functionality not already available elsewhere but it would give the ability to reuse potentially huge amounts of code. At the moment I'm having to decide which tests are important enough to run e2e and duplicate the code using TestRestTemplate.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

This looks very promising. I did a quick experiment. Essentially just one extra class that wraps a RestTemplate and applies adaptation to and from the MockHttpServletRequest and MockHttpSevletResponse. Also an interface so test fixtures can be run through MockMvc or the RestTemplate-backed alternative.

I'm going to schedule tentatively for 5.0 RC1 where we are also planning to put together test support for the spring-web-reactive module and this idea here would be useful to flesh out at this time. Thanks for the suggestion!

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

/cc robwinch and Andy Wilkinson

@spring-projects-issues
Copy link
Collaborator Author

Neil S commented

Great! Thanks very much :-)

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Jan 16, 2017

Rossen Stoyanchev commented

This needs to be considered together together with #18391 which comes from a different angle but is also about making MockMvc more broadly usable.

A separate concern that needs to be addressed is that many aspects of request building (request + session attributes + anything server-side specific) do not apply to integration tests. How do we deal with that is the question. We could fail fast or ignore those. The same concern is true for expectations matching (model attributes, handler method, etc) but there the matching would naturally fail..

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Note also that in 5.0 we have the WebTestClient which as actual client offers a MockMvc-like API that can be used with or without a server. Another route for addressing this ticket would be adapt it to work with both Spring MVC and WebFlux backends.

@spring-projects-issues spring-projects-issues added in: test Issues in the test module type: enhancement A general enhancement in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 5.x Backlog milestone Jan 11, 2019
@inanemed
Copy link

Could you please confirm this is still an issue?

@molexx
Copy link

molexx commented Jul 30, 2020

Yes.

@rstoyanchev 's experiment in #19647 (comment) looks to be a good solution.

@rstoyanchev rstoyanchev modified the milestones: 5.x Backlog, 5.3 RC1 Aug 4, 2020
@rstoyanchev rstoyanchev self-assigned this Aug 4, 2020
@rstoyanchev
Copy link
Contributor

I'll give this a try for 5.3 RC1.

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Aug 17, 2020

After some more thought, while my earlier experiment in #19647 (comment) does work, it has a major issue in that MockMvc is for server tests and exposes lots of request inputs and response assertions that cannot be passed over HTTP such as request and session attributes, contextPath, servletPath, model attributes, and many others. It makes it a poor choice for a common API for both mock HTTP and real HTTP tests.

A better path is via WebTestClient which already supports a common API for mock HTTP tests (with WebFlux) and for real HTTP tests. We can add support for mock HTTP tests (with Spring MVC) targeting MockMvc as the server. This support will expose equivalent setup options as are available in MockMvc, including standalone and ApplicationContext setups, adding filters, setup extension hooks (e.g. Spring Security setup), etc. but actual tests will be performed through the WebTestClient.

Among the benefits of this approach, as an actual HTTP client the WebTestClient provides encoding and decoding of request and response content (i.e. message conversion) which is convenient for assertions against higher level objects. For all other assertions in MockMvc, the WebTestClient offers equivalents but through a fluent API without static imports. Async requests will be a little easier to do through WebTestClient because the asyncDispatch is performed transparently.

I've experimented locally and it is fairly straight forward to do. I should have something concrete this week.

What does it mean for existing MockMvc tests and MockMvc? WebTestClient will use MockMvc to perform requests so MockMvc remains as the foundation for Spring MVC testing. I do expect however that a majority of tests will now be performed through the WebTestClient. Existing tests can remain as is, or it's easy to re-write them as well to WebTestClient.

rstoyanchev added a commit to rstoyanchev/spring-framework that referenced this issue Aug 17, 2020
rstoyanchev added a commit to rstoyanchev/spring-framework that referenced this issue Aug 17, 2020
@molexx
Copy link

molexx commented Aug 17, 2020

Sounds fair to me, thanks.

I've not looked at WebTestClient so far because we don't use Webflux, looking forward to checking it out.

rstoyanchev added a commit that referenced this issue Aug 19, 2020
The claimRequest method was not intended to be public and couldn't
have been used since the Info type it returned was package private.
This change completely hides the Info.

See gh-19647
rstoyanchev added a commit that referenced this issue Aug 19, 2020
The original behavior was to ignore the body which came with odd
warnings in the Javadoc and potential leaks that could be reported
from tests causing unnecessary concern.

This change causes the body to be released and effectively still
ignores it but minus the potential leaks.

See gh-19647
rstoyanchev added a commit that referenced this issue Aug 19, 2020
WebTestClient is an actual client and generally it's only possible
to assert the client response (i.e. what goes over HTTP). However,
in a mock server scenario technically we have access to the server
request and response and can make those available for further
assertions.

This will be helpful for the WebTestClient integration with MockMvc
where many more assertions can be performed on the server request
and response when needed.

See gh-19647
rstoyanchev added a commit that referenced this issue Aug 19, 2020
Provides parity with similar options in MockMvc:
 - compare header using a long value
 - compare header using a date/time value
 - dedicated method for "Location" header (redirect)
 - let Hamcrest assert a header even when missing

See gh-19647
rstoyanchev added a commit that referenced this issue Aug 19, 2020
rstoyanchev added a commit that referenced this issue Aug 19, 2020
@rstoyanchev
Copy link
Contributor

The new MockMvcTestClient is now available in master with 5.3 snapshots. I've ported all existing sample tests which can be used for comparison.

Before:

After

I am keeping the issue open until I've the documentation has been updated.

I've not looked at WebTestClient so far because we don't use Webflux

Use of WebTestClient requires hardly any knowledge of WebFlux. You'll see from the before and after tests the experience is largely the same. I've even managed to keep the ability to perform MockMvc assertions on the server response for those extra things like model attributes, flash attributes, etc.

@rstoyanchev rstoyanchev changed the title MockMvc compatible API for doing real HTTP tests [SPR-15081] Common API for MockMvc tests and for real HTTP tests Aug 19, 2020
@molexx
Copy link

molexx commented Aug 24, 2020

This looks great, thanks!

I think it would be useful to add an example that shows best practise to also run the tests from WebAppResourceTests in http mode please.

I'm not sure what the neatest way is? Thinking aloud, perhaps the tests could be in an abstract class that is extended by two concrete test classes - one setting up the MockMVCTestClient as shown and the other starting the http server with @SpringBootTest(webEnvironment=...) and leaving the WebTestClient to be autoconfigured. Or maybe the same test class could configure itself differently depending on a property passed at test runtime?

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Aug 25, 2020

I think it would be useful to add an example that shows best practise to also run the tests from WebAppResourceTests in http mode please.

Replace the test client initialization with something like this (and you'll also need to run the server):

this.testClient = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();

perhaps the tests could be in an abstract class that is extended by two concrete test classes - one setting up the MockMVCTestClient as shown and the other starting the http server with @SpringBootTest(webEnvironment=...) and leaving the WebTestClient to be autoconfigured.

Yes that's one way to do it. I suppose it depends on whether you want to run both every time or selectively. It probably makes sense to run in MockMvc mode most often and in live server mode once in a while. If setting things up programmatically you could use JUnit's Assume to check some environment variable or @ParameterizedTest to run both. Or in a more declarative style with Boot's @SpringBootTest(webEnvironment=...), I think JUnit's @EnabledIfEnvironmentVariable would be handy.

@molexx
Copy link

molexx commented Aug 25, 2020

Great thanks.

I think it's worth mentioning this in the docs to make it clear how easy it is to do.

rstoyanchev added a commit that referenced this issue Sep 1, 2020
jhoeller added a commit that referenced this issue Mar 15, 2021
@nvora
Copy link

nvora commented Apr 22, 2021

Do we have a reference project with an example of testing rest api with both and live server? if yes please share

lxbzmy pushed a commit to lxbzmy/spring-framework that referenced this issue Mar 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants