import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import org.junit.*; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; public class MyControllerTest { private MockMvc mockMvc; @Test public void test() throws Exception { mockMvc.perform(get("/my/test")) .andExpect(status().isOk()) .andExpect(content().string("")); } @Before public void setUp() throws Exception { mockMvc = MockMvcBuilders.standaloneSetup(new MyController()).build(); } }Nothing new, everything just as in spring's tutorial. So now let's write the code:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class MyController { @RequestMapping("/my/test") public void test() { System.out.println("test method executed"); //yes, yes, I know } }Test is green, that was easy. Let's see our code in action. Start jetty and check the address
/my/test
. Method is executed and we get... 404 not found. Wtf?!Probably the quickest way to find the problem is to compare the method with other working controllers. Of course, I forgot about
@ResponseBody
. After adding it, jetty displays the page correctly and test still passes. But the purpose of writing tests is to have protection against such mistakes. So why the test was green?For
void
methods without @ResponseBody
spring forwards request processing to DispatcherServlet
which, in this case, fails trying to resolve a view for the specified url. But for some reason mockMvc
reports empty response and status 200. I reported it as a bug but it got status 'Works as Designed'. So how can we eliminate the false positive? We need to explicitly check if no forwarding is done. And there is an existing ResultMatcher
for this:forwardedUrl(null)It can be added with another
andExpect
inside each test. But turning it on globally will save you from such mistakes in future:@Before public void setUp() throws Exception { mockMvc = MockMvcBuilders.standaloneSetup(new MyController()) .alwaysExpect(forwardedUrl(null)) .build(); }After adding this matcher, the test without
@ResponseBody
fails:java.lang.AssertionError: Forwarded URL expected:<null> but was:<my/test>