关于单元测试 Spring MVC 应用程序的说明
这是一篇关于单元测试 Spring MVC 应用程序的说明。
引言
这是一篇关于单元测试 Spring MVC 应用程序的说明。
背景
这是一篇关于 Spring MVC 应用程序单元测试的说明。 Spring 提供了一个 "spring-test" 包,它模拟了从 URL 到 MVC 控制器的路由过程。测试框架可以通过 URL 在控制器中运行代码。 两个附带的示例是相同的,除了其中一个使用 "TestNG",另一个使用 "JUnit"。
Maven 依赖
要创建 Spring MVC 应用程序并对其执行 Spring 风格的单元测试,您将需要以下依赖项。
<dependencies>
<!-- Servlet jars for compilation, provided by Tomcat -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<!-- Spring MVC dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.0.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
根据您选择的 TestNG 或 JUnit,您还需要选择性地添加以下依赖项之一。
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.9.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
示例 MVC 应用程序
附带示例中的简单 MVC 应用程序只有一个控制器,该控制器在 "TestController
" 类中实现。
package com.song.web.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.song.web.service.ExampleService;
@Controller
public class TestController {
@Autowired
private ExampleService exampleService;
@ResponseBody
@RequestMapping(value = "/example-end-point", method = RequestMethod.GET)
public Object test() {
return exampleService.getAMap();
}
}
“@Autowired”"ExampleService
" 实现如下
package com.song.web.service;
import java.util.HashMap;
import org.springframework.stereotype.Service;
@Service
public class ExampleService{
public HashMap<String, String> getAMap() {
HashMap<String, String> map = new HashMap<String, String>();
map.put("V", "This is from a Spring service.");
return map;
}
}
MVC 应用程序由 "MVCInitializer
" 类初始化。
package com.song.web.configuration;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.support
.AbstractAnnotationConfigDispatcherServletInitializer;
import com.song.web.filter.NocacheFilter;
import com.song.web.service.ExampleService;
public class MVCInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@EnableWebMvc
@ComponentScan({ "com.song.web.controller" })
public static class MVCConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
ResourceHandlerRegistration registration = registry.addResourceHandler("/*");
registration.addResourceLocations("/");
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public ExampleService exampleService() {
return new ExampleService();
}
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { MVCConfiguration.class };
}
@Override
public void onStartup(ServletContext servletContext)
throws ServletException {
FilterRegistration.Dynamic nocachefilter = servletContext
.addFilter("nocachefilter", new NocacheFilter());
nocachefilter.addMappingForUrlPatterns(null, false, "/*");
super.onStartup(servletContext);
}
@Override
protected String[] getServletMappings() {
return new String[] { "/api/*" };
}
@Override
protected Class<?>[] getRootConfigClasses() { return null; }
}
如果部署并运行该应用程序,并且您向 "https://:8080/ut-spring-mvc-testng/api/example-end-point" 发出 "GET
" 请求,您可以在 POSTMAN 中看到以下响应。
使用 TestNG 进行单元测试
为了在单元测试中启动 Spring 上下文,您需要一个实现 "WebMvcConfigurer" 接口的配置类。
package com.song.web.controller;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.song.web.service.ExampleService;
@EnableWebMvc
@ComponentScan({ "com.song.web.controller" })
public class TestMVCConfiguration implements WebMvcConfigurer {
@Bean
public ExampleService exampleService() {
return new ExampleService();
}
}
- “
@ComponentScan
”注解告诉 Spring 在哪里找到控制器。 - “
@Bean
”函数返回 "ExampleService
" 类的实例。 如果您不想使用实际实现,您可以返回该类的模拟对象用于单元测试目的。
"ControllerTest
" 类对 "TestController
" 执行测试。
package com.song.web.controller;
import java.util.HashMap;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
@WebAppConfiguration
@ContextConfiguration( classes = { TestMVCConfiguration.class })
public class ControllerTest extends AbstractTestNGSpringContextTests {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@BeforeMethod
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void ADummyTest() {
MockHttpServletRequestBuilder request
= MockMvcRequestBuilders.get("/example-end-point");
try {
ResultActions resultActions = mockMvc.perform(request);
MvcResult result = resultActions.andReturn();
MockHttpServletResponse response = result.getResponse();
int status = response.getStatus();
Assert.assertEquals(HttpServletResponse.SC_OK, status);
ObjectMapper mapper = new ObjectMapper();
HashMap<String, String> data = new HashMap<String, String>();
mapper.readerForUpdating(data).readValue(response.getContentAsString());
Assert.assertEquals(data.get("V"), "This is from a Spring service.");
} catch(Exception ex) { Assert.fail("Failed - " + ex.getMessage()); }
}
}
为了让 TestNG 能够将测试识别为 Spring 风格的单元测试,"ControllerTest
" 类需要继承 "AbstractTestNGSpringContextTests" 类。
使用 JUnit 进行单元测试
如果要使用 JUnit 执行测试,可以在 POM 依赖项中将 "testng" 替换为 "junit"。
package com.song.web.controller;
import java.util.HashMap;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
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.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith( SpringJUnit4ClassRunner.class )
@WebAppConfiguration
@ContextConfiguration( classes = { TestMVCConfiguration.class })
public class ControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void ADummyTest() {
MockHttpServletRequestBuilder request
= MockMvcRequestBuilders.get("/example-end-point");
try {
ResultActions resultActions = mockMvc.perform(request);
MvcResult result = resultActions.andReturn();
MockHttpServletResponse response = result.getResponse();
int status = response.getStatus();
Assert.assertEquals(HttpServletResponse.SC_OK, status);
ObjectMapper mapper = new ObjectMapper();
HashMap<String, String> data = new HashMap<String, String>();
mapper.readerForUpdating(data).readValue(response.getContentAsString());
Assert.assertEquals("This is from a Spring service.", data.get("V"));
} catch(Exception ex) { Assert.fail("Failed - " + ex.getMessage()); }
}
}
您需要用 "@RunWith( SpringJUnit4ClassRunner.class )
" 注解 "ControllerTest
" 类,而不是继承 "AbstractTestNGSpringContextTests" 类。
没有 Spring 注解的测试
如果您可以手动创建控制器实例,则无需 Spring 注解即可执行单元测试。
@Controller public class TestController { private ExampleService exampleService; @Autowired public TestController(final ExampleService exampleService) { this.exampleService = exampleService; } @ResponseBody @RequestMapping(value = "/example-end-point", method = RequestMethod.GET) public Object test() { return exampleService.getAMap(); } }
在 "TestController" 中,我们有一个构造函数来获取 "ExampleService",因此我们可以手动构造一个功能性控制器实例。
package com.song.web.controller; import java.util.HashMap; import javax.servlet.http.HttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.fasterxml.jackson.databind.ObjectMapper; import com.song.web.service.ExampleService; public class ControllerTest { private MockMvc mockMvc; @BeforeMethod public void setup() { this.mockMvc = MockMvcBuilders .standaloneSetup(new TestController(new ExampleService())).build(); } @Test public void ADummyTest() { MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/example-end-point"); try { ResultActions resultActions = mockMvc.perform(request); MvcResult result = resultActions.andReturn(); MockHttpServletResponse response = result.getResponse(); int status = response.getStatus(); Assert.assertEquals(HttpServletResponse.SC_OK, status); ObjectMapper mapper = new ObjectMapper(); HashMap<String, String> data = new HashMap<String, String>(); mapper.readerForUpdating(data).readValue(response.getContentAsString()); Assert.assertEquals(data.get("V"), "This is from a Spring service."); } catch(Exception ex) { Assert.fail("Failed - " + ex.getMessage()); } } }
我们可以通过将控制器实例传递给 "MockMvcBuilders .standaloneSetup()" 方法来创建 "MockMvc" 实例,而无需通过 Spring 上下文初始化并直接进行单元测试。
MVN & 内存
在构建过程中,如果项目变大,您可能会遇到内存问题。 由于内存问题,您也可能在单元测试中失败。 在这种情况下,我注意到以下命令可以缓解此问题。
mvn clean install -DargLine="-Xmx4096m"
您还可以更改 POM 以将 "-Xmx" 参数放入 SureFire 插件中。
关注点
- 这是一篇关于单元测试 Spring MVC 应用程序的说明。
- 希望您喜欢我的博文,并希望这篇笔记能以某种方式帮助您。
历史
- 2018 年 4 月 2 日:第一次修订