Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Testing Web Apps with Spring Framework

4.234 Aufrufe

Veröffentlicht am

Testing Web Apps with Spring Framework

Veröffentlicht in: Internet
  • Als Erste(r) kommentieren

Testing Web Apps with Spring Framework

  1. 1. Testing Web Apps with Spring Framework October 18, 2014 JavaDay’14, Kyiv Dmytro Chyzhykov
  2. 2. 3 Slides and Code Examples
  3. 3. - Build web applications with Spring Web MVC - Familiar with mock objects unit testing (JUnit and Mockito) 4 Assumptions on Audience
  4. 4. Spring Controllers Testing
 - Example Domain Model
 - Subject Under Test
 - Pure Unit Tests
 Spring MVC Test Framework
 - Standalone Server-Side Integration Tests
 - Web Application Context Server-Side Integration Tests Further materials Q&A 5 Agenda
  5. 5. Example Domain Model 6
  6. 6. 7 Yandex.TV Service
  7. 7. 8 Domain public class Channel { private Integer id; private String title; ! // constructors, getters/setters }
  8. 8. 9 Domain public class Channel { private Integer id; private String title; ! // constructors, getters/setters } DAO @Repository @Transactional public interface ChannelRepository { ! Channel findOne(Integer id); ! }
  9. 9. Subject Under Test 10
  10. 10. 11 Subject Under Test @Controller @RequestMapping("/channels") public class ChannelController { }
  11. 11. 12 Subject Under Test @Controller @RequestMapping("/channels") public class ChannelController { @Autowired ChannelRepository channelRepository; }
  12. 12. 13 Subject Under Test @Controller @RequestMapping("/channels") public class ChannelController { @Autowired ChannelRepository channelRepository; ! @ResponseBody @RequestMapping(value = "/{id}", method = RequestMethod.GET) public Channel getChannel(@PathVariable int id) { // ... } }
  13. 13. 14 Subject Under Test @Controller @RequestMapping("/channels") public class ChannelController { @Autowired ChannelRepository channelRepository; ! @ResponseBody @RequestMapping(value = "/{id}", method = RequestMethod.GET) public Channel getChannel(@PathVariable int id) { Channel channel = channelRepository.findOne(id); ! if (channel != null) { return channel; } ! // ... } }
  14. 14. 15 Subject Under Test @Controller @RequestMapping("/channels") public class ChannelController { @Autowired ChannelRepository channelRepository; ! @ResponseBody @RequestMapping(value = "/{id}", method = RequestMethod.GET) public Channel getChannel(@PathVariable int id) { Channel channel = channelRepository.findOne(id); ! if (channel != null) { return channel; } ! throw new ChannelNotFoundException(); } }
  15. 15. 16 Exception @ResponseStatus(HttpStatus.NOT_FOUND) public class ChannelNotFoundException extends RuntimeException { ! // constructors ! }
  16. 16. ChannelController behaviour - Positive test case
 When we are looking for an existent channel by its id - Negative test case
 When we are looking for an absent channel by its id 17 What we are going to test
  17. 17. Pure Unit Testing 18
  18. 18. 19http://www.tubelinescaffolding.co.uk/industrial-scaffolding.htm Scaffolding
  19. 19. 20 Unit Test Scaffolding ! public class ChannelControllerTest { }
  20. 20. 21 Unit Test Scaffolding ! public class ChannelControllerTest { @Mock private ChannelRepository channelRepository; }
  21. 21. 22 Unit Test Scaffolding ! public class ChannelControllerTest { @Mock private ChannelRepository channelRepository; ! @InjectMocks private ChannelController channelController = // optional new ChannelController(); }
  22. 22. 23 Unit Test Scaffolding @RunWith(MockitoJUnitRunner.class) public class ChannelControllerTest { @Mock private ChannelRepository channelRepository; ! @InjectMocks private ChannelController channelController = // optional new ChannelController(); }
  23. 23. 24 Unit Test Scaffolding @RunWith(MockitoJUnitRunner.class) public class ChannelControllerTest { @Mock private ChannelRepository channelRepository; ! @InjectMocks private ChannelController channelController = // optional new ChannelController(); ! ! @Mock private Channel channel; // dummy // test cases go here }
  24. 24. 25 Positive Test Case @Test public void itShouldFindChannel() { when(channelRepository.findOne(1)) .thenReturn(channel); }
  25. 25. 26 Positive Test Case @Test public void itShouldFindChannel() { when(channelRepository.findOne(1)) .thenReturn(channel); ! assertThat( channelController.getChannel(1), is(channel) ); }
  26. 26. 27 Negative Test Case @Test public void itShouldNotFoundChannel() { // optional when(channelRepository.findOne(-1)) .thenReturn(null); }
  27. 27. 28 Negative Test Case @Test(expected = ChannelNotFoundException.class) public void itShouldNotFoundChannel() { // optional when(channelRepository.findOne(-1)) .thenReturn(null); ! channelController.getChannel(-1); }
  28. 28. - Easy to write
 - Incredibly fast (a few milliseconds per test case) 29 Pros
  29. 29. - Can use Spring mocks from org.springframework.mock.web
 - MockHttpServletRequest/Response/Session
 - MockMultipartFile
 - MockFilterChain
 …
 - ModelAndViewAssert from org.springframework.test.web to apply asserts on a resulting ModelAndView 30 Additional Capabilities on Demand
  30. 30. - A lot left untested
 - Request mappings
 - Type conversion
 - Transactions
 - Data binding
 - Validation
 - Filters
 - …
 - No Spring annotations used
 - No DispatcherServlet interactions
 - No actual Spring MVC configuration loaded 31 Caveats
  31. 31. 32http://futurama.wikia.com/wiki/File:GoodNewsEveryone.jpg Good news everyone!
  32. 32. Spring MVC Test Framework since 3.2 33
  33. 33. <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-test</artifactId>
 <version>4.1.1.RELEASE</version>
 </dependency> 34 Dependency
  34. 34. 35 Server-Side Integration Testing without a Running Servlet Container Web
 Application
 Context DispatcherServlet Tests Controllers MockMvc
  35. 35. - Response status, headers, content
 - Spring MVC and Servlet specific results
 - Model, flash, session, request attributes
 - Mapped controller method
 - Resolved exceptions
 - Various options for asserting the response body
 - JsonPath, XPath, XMLUnit 36 What can be tested
  36. 36. - Almost all template technologies are supported
 - JSON, XML, Velocity, Freemarker, Thymeleaf, PDF etc. 
 - Except JSP (because it relies on Servlet Container)
 - you can assert only on the selected JSP view name
 - No actual redirecting or forwarding
 - you can assert the redirected or forwarded URL 37 Testing View Layer
  37. 37. Standalone setup for testing one individual controller at a time without actual Spring MVC configuration loading 38 MockMvc “Standalone” Setup private ChannelController controller = //... ! private MockMvc mockMvc; ! public void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(controller) .build(); }
  38. 38. 39 MockMvc “Standalone” Setup mockMvc = MockMvcBuilders.standaloneSetup(controller) .setValidator(...) .setViewResolvers(...) .setHandlerExceptionResolvers(...) .setMessageConverters(...) .setLocaleResolver(...) .addFilter(...) //... .build();
  39. 39. With actual Spring MVC configuration loading 40 MockMvc Web App Context Setup // Scaffolding is omitted ! @Autowired private WebApplicationContext wac; ! @Before public void setUp() { mockMvc = MockMvcBuilders.webAppContextSetup(wac) .build(); }
  40. 40. 41 Creating and Performing Requests MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/channels/1") .param("foo", "bar") .header(...) .cookie(...) .locale(...) .characterEncoding("UTF-8") .accept("application/json") .flashAttr("flash-key", "value") // ... .sessionAttr("key", “value"); ! ! mockMvc.perform(request);
  41. 41. 42 Applying Asserts mockMvc.perform(request) .andExpect(status().isOk()) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.id").value(1)) // ... .andExpect(xpath("...")...) .andExpect(header()...) .andExpect(cookies()...) .andExpect(model()...) .andExpect(view()...) .andExpect(content()...) .andExpect(flash()...) .andExpect(redirectedUrl("..."));
  42. 42. 43 Resolved Exception Assert MvcResult mvcResult = mockMvc .perform(...) // ... .andReturn(); ! assertThat( mvcResult.getResolvedException(), instanceOf(ChannelNotFoundException.class) );
  43. 43. - MockMvcBuilders.* to set up MockMvc instances
 - MockMvcRequestBuilders.* to create requests
 - MockMvcResultMatchers.* for request result assertions on 44 Useful Static Imports
  44. 44. Standalone Server-Side Integration Tests
  45. 45. 46 Scaffolding @RunWith(MockitoJUnitRunner.class) public class ChannelControllerStandaloneIT { @Mock private ChannelRepository channelRepository; @InjectMocks private ChannelController channelController = new ChannelController(); }
  46. 46. 47 Scaffolding @RunWith(MockitoJUnitRunner.class) public class ChannelControllerStandaloneIT { @Mock private ChannelRepository channelRepository; @InjectMocks private ChannelController channelController = new ChannelController(); private Channel channel = new Channel(1, "MTV"); }
  47. 47. 48 Scaffolding @RunWith(MockitoJUnitRunner.class) public class ChannelControllerStandaloneIT { @Mock private ChannelRepository channelRepository; @InjectMocks private ChannelController channelController = new ChannelController(); private Channel channel = new Channel(1, "MTV"); ! private MockMvc mockMvc; }
  48. 48. 49 Scaffolding @RunWith(MockitoJUnitRunner.class) public class ChannelControllerStandaloneIT { @Mock private ChannelRepository channelRepository; @InjectMocks private ChannelController channelController = new ChannelController(); private Channel channel = new Channel(1, "MTV"); ! private MockMvc mockMvc; ! @Before public void setUp() { mockMvc = standaloneSetup(channelController) .build(); } // test cases go here }
  49. 49. 50 Positive Test Case @Test public void itShouldFindChannel() throws Exception { when(channelRepository.findOne(1)) .thenReturn(channel); }
  50. 50. 51 Positive Test Case @Test public void itShouldFindChannel() throws Exception { when(channelRepository.findOne(1)) .thenReturn(channel); ! mockMvc.perform(get("/channels/1") .accept("application/json")) }
  51. 51. 52 Positive Test Case @Test public void itShouldFindChannel() throws Exception { when(channelRepository.findOne(1)) .thenReturn(channel); ! mockMvc.perform(get("/channels/1") .accept("application/json")) .andExpect(status().isOk()) }
  52. 52. 53 Positive Test Case @Test public void itShouldFindChannel() throws Exception { when(channelRepository.findOne(1)) .thenReturn(channel); ! mockMvc.perform(get("/channels/1") .accept("application/json")) .andExpect(status().isOk()) .andExpect(content() .contentType("application/json;charset=UTF-8")) }
  53. 53. 54 Positive Test Case @Test public void itShouldFindChannel() throws Exception { when(channelRepository.findOne(1)) .thenReturn(channel); ! mockMvc.perform(get("/channels/1") .accept("application/json")) .andExpect(status().isOk()) .andExpect(content() .contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.id").value(1)) }
  54. 54. 55 Positive Test Case @Test public void itShouldFindChannel() throws Exception { when(channelRepository.findOne(1)) .thenReturn(channel); ! mockMvc.perform(get("/channels/1") .accept("application/json")) .andExpect(status().isOk()) .andExpect(content() .contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.title").value("MTV")); }
  55. 55. 56 Negative Test Case @Test public void itShouldNotFindChannel() throws Exception { // optional when(channelRepository.findOne(-1)).willReturn(null); }
  56. 56. 57 Negative Test Case @Test public void itShouldNotFindChannel() throws Exception { // optional when(channelRepository.findOne(-1)).willReturn(null); ! mockMvc.perform(get("/channels/-1") .accept("application/json")) }
  57. 57. 58 Negative Test Case @Test public void itShouldNotFindChannel() throws Exception { // optional when(channelRepository.findOne(-1)).willReturn(null); ! mockMvc.perform(get("/channels/-1") .accept("application/json")) .andExpect(status().isNotFound()); }
  58. 58. 59 Negative Test Case @Test public void itShouldNotFindChannel() throws Exception { // optional when(channelRepository.findOne(-1)).willReturn(null); ! MvcResult mvcResult = mockMvc .perform(get("/channels/-1") .accept("application/json")) .andExpect(status().isNotFound()) .andReturn(); ! assertThat(mvcResult.getResolvedException(), instanceOf(ChannelNotFoundException.class)); }
  59. 59. 60 Demo
  60. 60. ChannelController instantiated Mock of ChannelRepository injected MockMvc was set-upped MockHttpServletRequest prepared Executed via DispatcherServlet Assertions applied on the resulting MockHttpServletResponse Assertions applied on the resulting MvcResult 61 What happened
  61. 61. - Easy to write
 - Uses Spring annotations
 - Always interacts with DispatcherServlet 62 Pros
  62. 62. - A bit slow (about 1 second for the first test case)
 - No Actual Spring MVC configuration loaded 63 Caveats
  63. 63. Web Application Context Server-Side Integration Tests
  64. 64. 65 Scaffolding ! ! ! ! ! ! ! ! public class ChannelControllerWebAppIT { }
  65. 65. 66 Scaffolding @RunWith(SpringJUnit4ClassRunner.class) ! ! ! ! ! ! ! public class ChannelControllerWebAppIT { }
  66. 66. 67 Scaffolding @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration ! ! ! ! ! ! public class ChannelControllerWebAppIT { @Autowired private WebApplicationContext wac; }
  67. 67. 68 Scaffolding @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration({ "file:somewhere/servlet-context.xml", "file:somewhere/persistence-context.xml" }) ! ! public class ChannelControllerWebAppIT { @Autowired private WebApplicationContext wac; }
  68. 68. 69 Scaffolding @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration({ "file:somewhere/servlet-context.xml", "file:somewhere/persistence-context.xml" }) @Transactional ! public class ChannelControllerWebAppIT { @Autowired private WebApplicationContext wac; }
  69. 69. 70 Scaffolding @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration({ "file:somewhere/servlet-context.xml", "file:somewhere/persistence-context.xml" }) @Transactional @Sql(scripts = "classpath:test-channel-seeds.sql") public class ChannelControllerWebAppIT { @Autowired private WebApplicationContext wac; }
  70. 70. 71 Scaffolding @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration({ "file:somewhere/servlet-context.xml", "file:somewhere/persistence-context.xml" }) @Transactional @Sql(scripts = "classpath:test-channel-seeds.sql") public class ChannelControllerWebAppIT { @Autowired private WebApplicationContext wac; ! private MockMvc mockMvc; ! @Before public void setUp() { mockMvc = webAppContextSetup(wac).build(); } }
  71. 71. 72 Positive Test Case @Test public void itShouldFindChannel() throws Exception { mockMvc.perform(get("/channels/1") .accept("application/json")) .andExpect(status().isOk()) .andExpect(content() .contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.title").value("MTV")); }
  72. 72. 73 Negative Test Case @Test public void itShouldNotFindChannel() throws Exception { MvcResult mvcResult = mockMvc .perform(get("/channels/-1") .accept("application/json")) .andExpect(status().isNotFound()) .andReturn(); ! assertThat(mvcResult.getResolvedException(), instanceOf(ChannelNotFoundException.class)); }
  73. 73. 74 Demo
  74. 74. Actual Web MVC application context loaded MockHttpServletRequest prepared Executed via DispatcherServlet Assertions applied on the resulting MockHttpServletResponse Assertions applied on the resulting MvcResult 75 What happened
  75. 75. - Easy to write
 - Loads actual Spring MVC configuration (cacheable)
 - Uses Spring annotations
 - Always Interacts with DispatcherServlet 76 Pros
  76. 76. - Slower than the “Standalone” option (depends on amount of beans in a particular Spring Mvc configuration)
 - Does not replace end-to-end testing like Selenium 77 Caveats
  77. 77. Further Materials
  78. 78. Integration between Spring MVC Test Framework and HtmlUnit. Repository: https://github.com/spring-projects/spring-test-htmlunit
 Documentation: https://github.com/spring-projects/spring-test- htmlunit/blob/master/src/asciidoc/index.adoc 79 Spring MVC Test HtmlUnit
  79. 79. Spring Framework Reference Documentation
 Chapter 11.3 Integration Testing
 http://docs.spring.io/spring/docs/current/spring-framework-reference/ htmlsingle/#integration-testing spring-test artifact source code
 https://github.com/spring-projects/spring-framework/tree/master/ spring-test Spring MVC Showcase
 https://github.com/spring-projects/spring-mvc-showcase Code examples
 https://github.com/ffbit/spring-mvc-test-framework-examples 80 Links
  80. 80. Webinar: Testing Web Applications with Spring 3.2
 by Sam Brannen (Swiftmind) and Rossen Stoyanchev
 https://www.youtube.com/watch?v=K6x8LE7Qd1Q Spring Testing
 by Mattias Severson
 https://www.youtube.com/watch?v=LYVJ69h76nw ! 81 Videos
  81. 81. Thank you! ! Questions?
  82. 82. ffbit@yandex-team.ru Dmytro Chyzhykov dmytro.chyzhykov@yandex.ru ffbit Senior Software Engineer at Yandex Media Services Kyiv, Ukraine @dcheJava
  83. 83. 84 Slides and Code Examples
  84. 84. 85http://www.dotatalk.com/wp-content/uploads/2013/09/All-hail-King-Hypno-Toad.jpg

×