Skip to content

Provide MockMvc support for Stateful HttpSession [SPR-13820] #18393

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 Dec 24, 2015 · 13 comments
Closed
Assignees
Labels
in: test Issues in the test module type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Dec 24, 2015

Rob Winch opened SPR-13820 and commented

In some instances it would be nice to provide support for stateful HttpSessions with MockMvc. I've had a few questions on SO about this (one example).

An example of how this could be done:

MockMvc mvc = ...
    .apply(statefulSessions())
    .build();


public class StatefulSessionSetup extends MockMvcConfigurerAdapter implements RequestPostProcessor, ResultHandler {

	private HttpSession session;

	private StatefulSessionSetup() {
	}
	public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> builder) {
		builder.alwaysDo(this);
	}
	public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder<?> builder, WebApplicationContext cxt) {
		return this;
	}
	public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
		if(session != null) {
			request.setSession(session);
		}
		return request;
	}
	public void handle(MvcResult result) throws Exception {
		this.session = result.getRequest().getSession(false);
	}

	public static MockMvcConfigurer statefulSessions() {
		return new StatefulSessionSetup();
	}
}

This isn't extremely well thought out, so it may be wise to organize the code better (i.e. separating the implementation for each interface), but this should get the idea across.


Affects: 4.3.3

Reference URL: http://stackoverflow.com/questions/32897406/how-can-i-share-a-mockmvc-session-with-htmlunit/32910451#32910451

Issue Links:

Referenced from: commits f5d2b88

1 votes, 4 watchers

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

How about using defaultRequest + one of the session-related request builder methods something like:

this.mockMvc = standaloneSetup(new TestController())
    .defaultRequest(get("/").session(session))
    .build()

@spring-projects-issues
Copy link
Collaborator Author

Rob Winch commented

Unless I'm missing something this will not work if the test requires that the first request does not contain a session.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Maybe I'm missing the point. I thought you want a stateful session re-used across requests. At any rate there is support for setting up session attributes both at the MockMvcBuilder level (as shown above) or per request directly on MockMvcRequestBuilder. What's missing?

@spring-projects-issues
Copy link
Collaborator Author

Rob Winch commented

Maybe I'm missing the point. I thought you want a stateful session re-used across requests.

I would like to easily emulate each test method run as a clean browser (i.e. cookies just cleared out) in terms of session. If the server does not create a session, then the request should not have a session present. If the request creates a session, then the next request within that method should automatically add the session from the previous request.

At any rate there is support for setting up session attributes both at the MockMvcBuilder level (as shown above) or per request directly on MockMvcRequestBuilder.

This is true. However, at the moment this is very manual for something that is a fairly common use case.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Okay I see what you mean now with multiple requests in the same test method. However MockMvc is typically re-used across all test methods in a Test class, so if one test method starts a session wouldn't that affect other subsequent test methods? Now that I understand better what did you think about setting the session on each request? It's explicit and transparent about the fact you're sharing a session. That seems like a good thing.

MockHttpSession session = new MockHttpSession();
this.mockMvc.perform((get("/foo").session(session))).andExpect(..);
this.mockMvc.perform((get("/bar").session(session))).andExpect(..);
...

@spring-projects-issues
Copy link
Collaborator Author

Rob Winch commented

Okay I see what you mean now with multiple requests in the same test method. However MockMvc is typically re-used across all test methods in a Test class, so if one test method starts a session wouldn't that affect other subsequent test methods?

I have not really seen this pattern. Typically I see a new instance of MockMvc used for each test method. The reference documentation creates a new instance of MockMvc in the setup method which means each test method has its own MockMvc instance.

Now that I understand better what did you think about setting the session on each request? It's explicit and transparent about the fact you're sharing a session. That seems like a good thing.

If I understand the proposal correctly, this doesn't quite meet my needs. Specifically, one of the requirements is that:

If the server does not create a session, then the request should not have a session present.

This means that the following is going to fail:

MockHttpSession session = new MockHttpSession();
this.mockMvc.perform(get("/does-not-create-session").session(session)).andExpect(nullSession());

To avoid this problem quite a bit more code is required.

MockHttpSession session = (MockHttpSession) this.mockMvc
    .perform(get("/a"))
    .andExpect(...)
    .andReturn().getRequest().getSession(false);

MockHttpServletRequestBuilder b = get("/b");
// b.session fails if the value is null
if(session != null) {
    b.session(session);
}
session = (MockHttpSession) this.mockMvc
    .perform(b)
    .andExpect(...)
    .andReturn().getRequest().getSession(false);

MockHttpServletRequestBuilder c = get("/c");
// c.session fails if the value is null
if(session != null) {
    c.session(session);
}
session = (MockHttpSession) this.mockMvc
    .perform(c)
    .andExpect(...)
    .andReturn().getRequest().getSession(false);

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Okay, sorry it took me a while to get this. I think I do now :)

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

hey robwinch looking at your original proposal at the top, wouldn't the StatefulSessionSetup need to be created once and stored so it is re-used across all tests methods that want a shared session? As far as I can see the static statefulSessions() method re-creates on each call, i.e. when a new MockMvc is created.

@spring-projects-issues
Copy link
Collaborator Author

Rob Winch commented

It depends on the scope of the shared session. My sample was intended to share the session for multiple requests within the same test. In my opinion, this is more likely how it would be used because there are no guarantees on the order the tests are executed in.

Of course if you want, the StatefulSessionSetup can be saved to a static variable and reused across all the tests. The scope is up to the user.

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

Hmmmm.... sounds to me like the scope of such a session will then need to be configurable by the user.

I can imagine users wanting it:

  • per test method
  • per test class
  • per test suite (i.e., globally, not "suite" in the sense of a JUnit 4 or TestNG suite)

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Okay thanks Rob and Sam.

I think tests using a shared session are likely to be sensitive to the order of test execution indeed. So it's hard to see how a session shared "per test suite" could be useful. At the class level there seem to be some options for the order of test execution so I guess that's an option but I tend to agree with Rob this feature is most likely to be used in a test method that defines the entire scenario with multiple requests. Otherwise you'd be splitting a scenario into ordered tests with each test method representing one request and then whole class representing the scenario, which seems a lot less convenient and I'm not sure it's worth encouraging that with people then running into issues with method ordering.

Technically one could create a MockMvc for the entire class (i.e. @BeforeClass) and still achieve the same with Rob's proposal, so that looks good to me.

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

I think tests using a shared session are likely to be sensitive to the order of test execution indeed. So it's hard to see how a session shared "per test suite" could be useful.

I agree: "per test suite" is actually a bad idea.

At the class level there seem to be some options for the order of test execution so I guess that's an option but I tend to agree with Rob this feature is most likely to be used in a test method that defines the entire scenario with multiple requests. Otherwise you'd be splitting a scenario into ordered tests with each test method representing one request and then whole class representing the scenario, which seems a lot less convenient and I'm not sure it's worth encouraging that with people then running into issues with method ordering.

This only applies to JUnit 4.

TestNG has always supported such scenario tests, and JUnit Jupiter will eventually support scenario tests as well. Note that the example I provided in the linked JUnit 5 issue deals exactly with Spring Security testing scenarios. ;)

So we have to keep such testing use cases in mind.

Technically one could create a MockMvc for the entire class (i.e. @BeforeClass) and still achieve the same with Rob's proposal, so that looks good to me.

That would be the work-around for JUnit 4, along with @FixMethodOrder. For TestNG and JUnit Jupiter, it's not an issue as long as the developer creates an instance of MockMvc per test instance (i.e., stores it in a field) and as long as the testing framework uses the same test instance across test methods -- which is always the case for TestNG and will be the case for scenario tests in JUnit Jupiter.

In other words, I agree: bind the scope of the session to the MockMvc instance. ;-)

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Great, thanks for the detail Sam!

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 type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants