Springboot---mockMVC进行Controller单元测试

本文介绍如何使用SpringBoot进行单元测试,包括配置测试环境、使用MockMvc模拟HTTP请求及响应验证,同时展示了如何处理JSON对象中null值的序列化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

知识准备

1、注解含义

@RunWith(SpringRunner.class):表示使用Spring Test组件进行单元测试,其中SpringRunner继承类SpringJUnit4ClassRunner。 
@WebAppConfiguration:测试环境使用,用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的;value指定web应用的根; 
@AutoConfigureMockMvc:注入一个MockMvc实例;

注入web环境的ApplicationContext容器:mvc = MockMvcBuilders.webAppContextSetup(context).build(); 
2、fastjson转换--对象字段值为null的json化

        //方式一:
        /**
         * 自定义jason转换字段过路规则
         */
        ValueFilter filter = new ValueFilter() {
            @Override
            public Object process(Object object, String name, Object value) {
                if (value == null)
                    return "";//如果字段的值为空则用空字符串代替
                return value;
            }
        };
        JSONObject.toJSONString(lisList,filter )
 
        // 方式二:       
        // 如果lisList字段的值为空,则用null表示
        JSONObject.toJSONString(lisList,SerializerFeature.WriteMapNullValue)

单元测试

依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

代码 

1、jsonPath("$.result")的规则项目地址:https://github.com/json-path/JsonPath

2、使用ObjectMapper等同于对Girl对象直接赋值再转换为json格式。

@RunWith(SpringRunner.class)
@SpringBootTest// 此处若不指定,则默认为src/main/java/下面的启动类
public class TermControllerTest {

    private MockMvc mockMvc;

    private static final String PREFIX_URI = "XXXXXXX";

    /**
     * 注入WebApplicationContext
     */
    @Autowired
    private WebApplicationContext webApplicationContext;

    /**
     * 模拟MVC对象,通过MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build()初始化
     */
    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(
                this.webApplicationContext).build();
    }

    /**
     * 获取所有字典类别,类型为basicitems
     * 
     * @throws Exception
     */
    @Test
    public void loadTermsByTypes() throws Exception {

        String uri = PREFIX_URI + "/basic/getList" + "?types=changestatus";
        
        // 构造返回值对象
        Response<List<List<Map<String, String>>>> fpoResponse = new FpoResponse<>();
        Response.setCode("xxx");
        ... ...
        
        // 模拟GET请求
        MvcResult mvcResult = this.mockMvc
                .perform(MockMvcRequestBuilders.get(uri))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(// 校验自定义的状态码
                        MockMvcResultMatchers.jsonPath("$.code").value(
                                "xxxxxxx"))
                .andExpect(
                        MockMvcResultMatchers.content().contentType(
                                MediaType.APPLICATION_JSON_UTF8))
                .andExpect(
                        MockMvcResultMatchers.jsonPath("$.result").value(// 获取所有结果
                                JSONObject.toJSONString(lisList,// 指定空字符串也进行转换
                                        SerializerFeature.WriteMapNullValue)))
                .andReturn();// 获取返回结果
        /**
         * 自定义jason转换字段过路规则
         */
        ValueFilter filter = new ValueFilter() {
            @Override
            public Object process(Object object, String name, Object value) {
                if (value == null)
                    return "";
                return value;
            }
        };
        
        // 打印返回结果
        System.out.println(JSONObject.toJSONString(Response,
                SerializerFeature.WriteMapNullValue));
        System.out.println(mvcResult.getResponse().getContentAsString());
    }


    @Test
    public void addGirl() throws Exception {
//        Girl girl = new Girl();
//        girl.setCupSize("B");
//        girl.setAge(19);
//        girl.setMoney(22.22);

        //ObjectMapper 是一个可以重复使用的对象
        ObjectMapper mapper = new ObjectMapper();
        String jsonString = "{\"cupSize\":\"B\", \"age\":19, \"money\":22.22}";
        //将JSON字符串值转换成 Girl对象里的属性值
        Girl girl = mapper.readValue(jsonString, Girl.class);
        mvc.perform(MockMvcRequestBuilders.post("/girlsss")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
        /* 使用writeValueAsString() 方法来获取对象的JSON字符串表示 */
                .content(mapper.writeValueAsString(girl)))
                .andExpect(MockMvcResultMatchers.status().isOk())
                              .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
                 .andExpect(MockMvcResultMatchers.jsonPath(
                        "$.cupSize").value("B"))
                .andExpect(MockMvcResultMatchers.jsonPath(
                        "$.age").value(19))
                .andExpect(MockMvcResultMatchers.jsonPath(
                        "$.money").value(22.22));
    }
}

单元测试事务

junit 单元测试事务会自动回滚。通过@Rollback(true)注解来实现,默认是true,事务会回滚,可以不写。false时事务不会回滚,数据会写到数据库中。

@Transactional也去掉再运行测试,结果在数据库中查询到了被插入的数据

结论:在JUnit4的测试方法中打事务注解@Transactional,默认会按照@Rollback(true)来进行处理,无论如何都会回滚,打不打注解@Rollback@Rollback(true)已经不重要了。而在@Transactional的基础上加上@Rollback(false)之后,效果就好像是单单这个测试函数这一层没有打事务似的,而不会传播到嵌套的service层的事务内,即如果在service层的事务中,插入数据后又发生异常,最终在service层里还是会进行rollback,数据并不会插入到数据库中。

Case 1: 插入成功后会回滚
        输出:insert :1 rows!
             Rolled back transaction after test execution for test context......
    @Test
    @Transactional
    public void testTx() throws Exception{
        User user = new User();
        user.setUsername("zhuqiuhui-testTx");
        user.setAddress("河南省上蔡县-testTx");
        user.setBirthday(new Date());
        user.setSex("男-testTx");
        int num = iUserService.insertUser(user);
        assert(num==1);
        System.out.println("insert :" + num + " rows!");
    }
  
Case 2: 正常插入,此过程若(1)处抛出异常(不论Exception、RuntimeException或者FileNotFoundException等)均正常插入,事务并不回滚
    @Test
    @Transactional
    @Rollback(false)
    public void testTx() throws Exception{
        User user = new User();
        user.setUsername("zhuqiuhui-testTx");
        user.setAddress("河南省上蔡县-testTx");
        user.setBirthday(new Date());
        user.setSex("男-testTx");
        int num = iUserService.insertUser(user);--------(1)
        assert(num==1);
        System.out.println("insert :" + num + " rows!");
    }

 

<think>好的,我现在要解决用户的问题:如何在Spring Boot中对Controller进行单元测试,并且需要给出示例。首先,我需要回顾一下用户提供的引用内容,看看有没有相关的信息可以利用。 用户提供的引用3和4都提到了Controller层的测试。引用3展示了一个Controller类的代码,里面有GET和POST方法的示例。引用4则提到了使用MockMvc和doAnswer来模拟响应,这可能涉及到如何模拟依赖的行为。另外,引用1和2主要讲的是Spring Boot单元测试的基础配置,比如@SpringBootTest和@Autowired的使用,这些是测试Controller的基础。 接下来,我需要整理一下Spring Boot中测试Controller的常用方法。通常有两种方式:一种是使用@SpringBootTest注解启动完整的Spring上下文,结合MockMvc进行集成测试;另一种是使用WebMvcTest进行切片测试,只加载Controller相关的上下文,并配合MockBean来模拟Service层。这两种方法各有优劣,用户可能需要知道这两种方式。 用户请求的是示例,所以需要给出具体的代码示例。比如,针对引用3中的WebController,如何编写测试用例。同时要考虑到不同的HTTP方法(GET、POST)以及如何处理请求体和请求头。此外,是否需要模拟Service层,比如使用Mockito来mock依赖,这样测试可以更专注在Controller的逻辑上,而不是整个应用流程。 然后,我需要确保代码示例符合用户的要求,比如使用正确的注解,如@RunWith(SpringRunner.class)或者@SpringBootTest,以及如何配置MockMvc。引用2中的CalculatorServiceTest示例可能对如何编写测试方法有帮助,但需要调整到Controller的测试场景。 另外,用户可能关心测试的独立性和速度,所以使用WebMvcTest可能更适合单元测试,因为它只加载部分上下文,启动更快。而@SpringBootTest适合集成测试,但启动时间较长。需要根据具体情况建议。 还要注意测试中的断言,比如检查HTTP状态码、响应内容是否正确,以及是否处理了请求参数和请求体。引用4中提到的doAnswer和断言的使用,可能在模拟DAO层时有用,但在Controller测试中,可能更多使用MockMvc的perform和andExpect方法。 最后,要确保回答结构清晰,分步骤讲解,代码示例正确,并且符合用户提到的格式要求,比如行内数学公式用$符号,但这里可能用不到数学公式。不过需要检查是否有需要LaTeX的地方,比如没有的话就不用加。 总结步骤: 1. 介绍两种测试方式:集成测试(@SpringBootTest + MockMvc)和单元测试(@WebMvcTest + MockBean)。 2. 分别给出两种方式的示例代码,比如测试GET和POST方法。 3. 解释代码中的关键注解和方法,如@AutoConfigureMockMvc,@MockBean,MockMvc的perform、andExpect等。 4. 可能提到如何模拟Service层,使用Mockito.when来处理依赖方法。 5. 强调测试的覆盖点和断言的重要性。 需要验证代码是否正确,比如是否正确处理了@RequestHeader,如何传递JSON内容,以及使用MockMvc的请求构建方法。同时,确保引用的内容被正确参考,比如引用3中的WebController,在测试示例中是否对应。 可能的问题点:是否需要启动完整的Spring上下文?如果使用@WebMvcTest,是否需要指定Controller类?是的,通常@WebMvcTest(WebController.class)来限定只加载该Controller的配置。 最后,生成相关问题,帮助用户进一步了解,比如如何模拟依赖、如何处理异常测试、如何测试文件上传等。</think>在Spring Boot中对Controller进行单元测试,主要使用`MockMvc`模拟HTTP请求,并结合`@WebMvcTest`或`@SpringBootTest`注解。以下是具体实现步骤和示例: --- ### 方法一:使用@WebMvcTest(推荐用于纯Controller层测试) 仅加载Controller相关配置,配合`@MockBean`模拟Service依赖,适合快速单元测试。 ```java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(WebController.class) // 指定测试的Controller类 public class WebControllerTest { @Autowired private MockMvc mockMvc; @MockBean // 模拟Service依赖 private MyService myService; @Test public void testGet() throws Exception { mockMvc.perform(get("/web/get")) // 模拟GET请求 .andExpect(status().isOk()) // 断言HTTP状态码 .andExpect(content().string("get")); // 断言响应内容 } @Test public void testPost() throws Exception { String requestBody = "{\"name\":\"Alice\",\"age\":20}"; mockMvc.perform(post("/web/post") .contentType("application/json") .content(requestBody)) .andExpect(status().isOk()) .andExpect(content().string("post success")); } @Test public void testPostWithHeader() throws Exception { mockMvc.perform(post("/web/postHead") .header("token", "abc123")) // 设置请求头 .andExpect(status().isOk()) .andExpect(content().string("post head token:abc123")); } } ``` --- ### 方法二:使用@SpringBootTest(集成测试) 启动完整Spring上下文,适合需要测试完整调用链的场景。 ```java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; @SpringBootTest @AutoConfigureMockMvc // 自动配置MockMvc public class WebControllerIntegrationTest { @Autowired private MockMvc mockMvc; @Test public void testGetIntegration() throws Exception { mockMvc.perform(get("/web/get")) .andExpect(status().isOk()); } } ``` --- ### 关键注解说明 1. **`@WebMvcTest`** 仅初始化Controller层,需配合`@MockBean`模拟依赖[^3]。 2. **`@SpringBootTest`** 加载完整Spring上下文,适合集成测试[^2]。 3. **`MockMvc`** 提供`perform()`方法模拟HTTP请求,支持链式断言[^4]。 4. **`@MockBean`** 替换Spring上下文中的实际Bean为Mock对象,例如: ```java @MockBean private UserService userService; when(userService.findById(1L)).thenReturn(new User("Test")); // Mock方法调用 ``` --- ### 测试覆盖场景 1. **请求参数验证** 使用`param()`方法传递URL参数: ```java mockMvc.perform(get("/api/user").param("id", "1")) ``` 2. **JSON请求体处理** 通过`content()`传递JSON字符串,并设置`contentType("application/json")`。 3. **异常测试** 断言特定异常状态码: ```java .andExpect(status().isBadRequest()) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值