65.9K
CodeProject 正在变化。 阅读更多。
Home

关于单元测试 Spring MVC 应用程序的说明

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2018年4月3日

CPOL

2分钟阅读

viewsIcon

13508

downloadIcon

171

这是一篇关于单元测试 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 日:第一次修订
© . All rights reserved.