It is now time to write our first unit test.
We will focus on writing tests at the controller level because we have little to no business code or service. The key to writing tests for Spring MVC is the org.springframework.boot:spring-boot-starter-test
dependency in our classpath. It will add a few very useful libraries, such as these:
hamcrest
: This is JUnit's assertion librarymockito
: This is a mocking libraryspring-test
: This is the Spring testing libraryWe will test the redirection to the profile page that is created when the user hasn't created their profile yet.
We already have an autogenerated test called MasterSpringMvc4ApplicationTests
. It is the most basic kind of test one can write with the Spring test framework: it does nothing but blow up if the context cannot be loaded:
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MasterSpringMvc4Application.class) @WebAppConfiguration public class MasterSpringMvc4ApplicationTests { @Test public void contextLoads() { } }
We can delete this test and create one that will ensure that a user with no profile will be redirected to the profile page by default. It will actually test the code of the HomeController
class, so let's call it HomeControllerTest
class and put it in the same package as HomeController
, in src/test/java
. All IDEs have shortcuts for creating a JUnit test case from a class. Find out how to do it with yours now!
Here is the test:
package masterSpringMvc.controller; import masterSpringMvc.MasterSpringMvcApplication; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MasterSpringMvcApplication.class) @WebAppConfiguration public class HomeControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test public void should_redirect_to_profile() throws Exception { this.mockMvc.perform(get("/")) .andDo(print()) .andExpect(status().isFound()) .andExpect(redirectedUrl("/profile")); } }
We use MockMvc
to simulate interactions with a Spring controller without the actual overhead of a Servlet container.
We also use a couple of matchers that Spring provides to assert our result. They actually implement Hamcrest matchers.
The .andDo(print())
statement will produce a neat debug output for the request and response of the scenario under test. You can comment it if you find it too verbose.
That's all there is to it! The syntax is a bit tricky at the beginning, but an IDE with good completion will be able to help you.
Now we want to test whether, if the user has filled in the test part of their profile, we can redirect them to the correct search. For that, we will need to stub the session with the MockHttpSession
class:
import org.springframework.mock.web.MockHttpSession; import masterSpringMvc.profile.UserProfileSession; // put this test below the other one @Test public void should_redirect_to_tastes() throws Exception { MockHttpSession session = new MockHttpSession(); UserProfileSession sessionBean = new UserProfileSession(); sessionBean.setTastes(Arrays.asList("spring", "groovy")); session.setAttribute("scopedTarget.userProfileSession", sessionBean); this.mockMvc.perform(get("/").session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/search/mixed;keywords=spring,groovy")); }
You will have to add the setTastes()
setter to the UserProfileSession
bean for the test to work.
There are a lot of mocking utilities for the Servlet environment in the org.springframework.mock.web
package.
Note that the attribute representing our bean in session is prefixed by scopedTarget
. That's because session beans are proxified by Spring. Therefore, there are actually two objects in the Spring context, the actual bean that we defined and its proxy that will end up in the session.
The mock session is a neat class, but we can refactor the test with a builder that will hide implementation details and can be reused later:
@Test public void should_redirect_to_tastes() throws Exception { MockHttpSession session = new SessionBuilder().userTastes("spring", "groovy").build(); this.mockMvc.perform(get("/") .session(session)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/search/mixed;keywords=spring,groovy")); }
The code for the builder is as follows:
public class SessionBuilder { private final MockHttpSession session; UserProfileSession sessionBean; public SessionBuilder() { session = new MockHttpSession(); sessionBean = new UserProfileSession(); session.setAttribute("scopedTarget.userProfileSession", sessionBean); } public SessionBuilder userTastes(String... tastes) { sessionBean.setTastes(Arrays.asList(tastes)); return this; } public MockHttpSession build() { return session; } }
After this refactoring, your test should always pass, of course.