Upload
dmytro-chyzhykov
View
446
Download
1
Embed Size (px)
DESCRIPTION
Testing Web Apps with Spring Framework
Citation preview
Testing Web Apps with Spring Framework
October 18, 2014 JavaDay’14, Kyiv
Dmytro Chyzhykov
3
Slides and Code Examples
- Build web applications with Spring Web MVC
- Familiar with mock objects unit testing (JUnit and Mockito)
4
Assumptions on Audience
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
Example Domain Model
6
7
Yandex.TV Service
8
Domainpublic class Channel { private Integer id; private String title; ! // constructors, getters/setters }
9
Domainpublic class Channel { private Integer id; private String title; ! // constructors, getters/setters }
DAO@Repository @Transactional public interface ChannelRepository { ! Channel findOne(Integer id); !}
Subject Under Test
10
11
Subject Under Test@Controller @RequestMapping("/channels") public class ChannelController { }
12
Subject Under Test@Controller @RequestMapping("/channels") public class ChannelController { @Autowired ChannelRepository channelRepository; }
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) { // ... } }
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; } ! // ... } }
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(); } }
16
Exception@ResponseStatus(HttpStatus.NOT_FOUND) public class ChannelNotFoundException extends RuntimeException { ! // constructors !}
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
Pure Unit Testing
18
19http://www.tubelinescaffolding.co.uk/industrial-scaffolding.htm
Scaffolding
20
Unit Test Scaffolding!public class ChannelControllerTest { }
21
Unit Test Scaffolding!public class ChannelControllerTest { @Mock private ChannelRepository channelRepository; }
22
Unit Test Scaffolding!public class ChannelControllerTest { @Mock private ChannelRepository channelRepository; ! @InjectMocks private ChannelController channelController = // optional new ChannelController(); }
23
Unit Test Scaffolding@RunWith(MockitoJUnitRunner.class) public class ChannelControllerTest { @Mock private ChannelRepository channelRepository; ! @InjectMocks private ChannelController channelController = // optional new ChannelController(); }
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 }
25
Positive Test Case@Test public void itShouldFindChannel() { when(channelRepository.findOne(1)) .thenReturn(channel); }
26
Positive Test Case@Test public void itShouldFindChannel() { when(channelRepository.findOne(1)) .thenReturn(channel); ! assertThat( channelController.getChannel(1), is(channel) ); }
27
Negative Test Case@Test public void itShouldNotFoundChannel() { // optional when(channelRepository.findOne(-1)) .thenReturn(null); }
28
Negative Test Case@Test(expected = ChannelNotFoundException.class) public void itShouldNotFoundChannel() { // optional when(channelRepository.findOne(-1)) .thenReturn(null); ! channelController.getChannel(-1); }
- Easy to write - Incredibly fast (a few milliseconds per test case)
29
Pros
- 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
- 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
32http://futurama.wikia.com/wiki/File:GoodNewsEveryone.jpg
Good news everyone!
Spring MVC Test Framework since 3.2
33
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.1.1.RELEASE</version></dependency>
34
Dependency
35
Server-Side Integration Testing without a Running Servlet Container
Web Application
ContextDispatcherServlet
Tests
Controllers
MockMvc
- 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
- 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
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(); }
39
MockMvc “Standalone” SetupmockMvc = MockMvcBuilders.standaloneSetup(controller) .setValidator(...) .setViewResolvers(...) .setHandlerExceptionResolvers(...) .setMessageConverters(...) .setLocaleResolver(...) .addFilter(...) //... .build();
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(); }
41
Creating and Performing RequestsMockHttpServletRequestBuilder 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);
42
Applying AssertsmockMvc.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("..."));
43
Resolved Exception AssertMvcResult mvcResult = mockMvc .perform(...) // ... .andReturn(); ! assertThat( mvcResult.getResolvedException(), instanceOf(ChannelNotFoundException.class) );
- MockMvcBuilders.* to set up MockMvc instances- MockMvcRequestBuilders.* to create requests- MockMvcResultMatchers.* for request result assertions on
44
Useful Static Imports
Standalone Server-Side Integration Tests
46
Scaffolding@RunWith(MockitoJUnitRunner.class) public class ChannelControllerStandaloneIT { @Mock private ChannelRepository channelRepository; @InjectMocks private ChannelController channelController = new ChannelController(); }
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"); }
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; }
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 }
50
Positive Test Case@Test public void itShouldFindChannel() throws Exception { when(channelRepository.findOne(1)) .thenReturn(channel); }
51
Positive Test Case@Test public void itShouldFindChannel() throws Exception { when(channelRepository.findOne(1)) .thenReturn(channel); ! mockMvc.perform(get("/channels/1") .accept("application/json")) }
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()) }
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")) }
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)) }
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")); }
56
Negative Test Case@Test public void itShouldNotFindChannel() throws Exception { // optional when(channelRepository.findOne(-1)).willReturn(null); }
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")) }
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()); }
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)); }
60
Demo
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
- Easy to write - Uses Spring annotations- Always interacts with DispatcherServlet
62
Pros
- A bit slow (about 1 second for the first test case)- No Actual Spring MVC configuration loaded
63
Caveats
Web Application Context Server-Side Integration Tests
65
Scaffolding!!!!!!!!public class ChannelControllerWebAppIT { }
66
Scaffolding@RunWith(SpringJUnit4ClassRunner.class) !!!!!!!public class ChannelControllerWebAppIT { }
67
Scaffolding@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration !!!!!!public class ChannelControllerWebAppIT { @Autowired private WebApplicationContext wac; }
68
Scaffolding@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration({ "file:somewhere/servlet-context.xml", "file:somewhere/persistence-context.xml" }) !!public class ChannelControllerWebAppIT { @Autowired private WebApplicationContext wac; }
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; }
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; }
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(); } }
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")); }
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)); }
74
Demo
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
- Easy to write - Loads actual Spring MVC configuration (cacheable)- Uses Spring annotations- Always Interacts with DispatcherServlet
76
Pros
- 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
Further Materials
Integration between Spring MVC Test Framework and HtmlUnit.
Repository: https://github.com/spring-projects/spring-test-htmlunitDocumentation: https://github.com/spring-projects/spring-test-htmlunit/blob/master/src/asciidoc/index.adoc
79
Spring MVC Test HtmlUnit
Spring Framework Reference DocumentationChapter 11.3 Integration Testinghttp://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 exampleshttps://github.com/ffbit/spring-mvc-test-framework-examples
80
Links
Webinar: Testing Web Applications with Spring 3.2 by Sam Brannen (Swiftmind) and Rossen Stoyanchevhttps://www.youtube.com/watch?v=K6x8LE7Qd1Q
Spring Testingby Mattias Seversonhttps://www.youtube.com/watch?v=LYVJ69h76nw
!
81
Videos
Thank you! !
Questions?
Dmytro Chyzhykov
ffbit
Senior Software Engineer at Yandex Media ServicesKyiv, Ukraine
@dcheJava
84
Slides and Code Examples
85http://www.dotatalk.com/wp-content/uploads/2013/09/All-hail-King-Hypno-Toad.jpg