You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot单元测试中数据源Mock配置与实现技术咨询

Spring Boot Controller单元测试:数据源Mock与验证指南

嘿,我来帮你一步步搞定这个Spring Boot单元测试里的数据源Mock和验证问题。你已经搭好了基础的测试配置,接下来咱们把Mock逻辑补全,再加上验证步骤就齐活了。

1. 编写Repository的Mock逻辑

首先在你的setupMock方法里,咱们先准备测试用的实体数据,然后用Mockito来Stub(打桩)Repository的方法,让它返回咱们预设好的数据,而不是真的去连接数据库。

示例代码:

import java.util.Arrays;
import java.util.Optional;
import org.mockito.Mockito;
import org.springframework.test.annotation.Before;

// 你的测试类已有的配置...
public class ControllerTests {
    @Autowired
    private MockMvc mvc;
    @MockBean
    private ProductRepository productRepo;
    @MockBean
    private MonitorRepository monitorRepo;

    @Before
    public void setupMock() {
        // 1. 准备Product测试数据
        Product testProduct = new Product();
        testProduct.setId(1L);
        testProduct.setName("Test Wireless Headphones");
        testProduct.setPrice(199.99);
        List<Product> testProductList = Arrays.asList(testProduct);

        // 2. Stub ProductRepository的常用方法
        // 当调用findAll()时,返回预设的商品列表
        Mockito.when(productRepo.findAll()).thenReturn(testProductList);
        // 当调用findById(1L)时,返回包含测试商品的Optional
        Mockito.when(productRepo.findById(1L)).thenReturn(Optional.of(testProduct));
        // 当查询不存在的ID时,返回空Optional
        Mockito.when(productRepo.findById(999L)).thenReturn(Optional.empty());

        // 3. 同理处理MonitorRepository
        Monitor testMonitor = new Monitor();
        testMonitor.setId(100L);
        testMonitor.setBrand("Dell");
        testMonitor.setSize(27);
        Mockito.when(monitorRepo.findById(100L)).thenReturn(Optional.of(testMonitor));
    }

    // 后面的测试方法写在这里...
}

2. 编写Controller请求测试与验证

接下来咱们用MockMvc发送HTTP请求,验证接口返回的JSON响应是否符合预期,同时还要验证Repository的方法是否被正确调用(确保Controller逻辑和Repository的交互是对的)。

2.1 测试GET接口返回JSON

import org.junit.Test;
import org.springframework.http.MediaType;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.mockito.Mockito.verify;

@Test
public void testGetAllProducts() throws Exception {
    // 发送GET请求到/api/products
    mvc.perform(get("/api/products")
            .contentType(MediaType.APPLICATION_JSON))
            // 验证响应状态码是200 OK
            .andExpect(status().isOk())
            // 验证JSON响应里第一个商品的ID和名称
            .andExpect(jsonPath("$[0].id").value(1L))
            .andExpect(jsonPath("$[0].name").value("Test Wireless Headphones"));

    // 验证ProductRepository的findAll()方法被调用了一次
    verify(productRepo, Mockito.times(1)).findAll();
}

@Test
public void testGetProductById() throws Exception {
    // 测试存在的商品ID
    mvc.perform(get("/api/products/1")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(1L));

    // 验证findById(1L)被调用了一次
    verify(productRepo, Mockito.times(1)).findById(1L);

    // 测试不存在的商品ID
    mvc.perform(get("/api/products/999")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isNotFound());

    // 验证findById(999L)被调用了一次
    verify(productRepo, Mockito.times(1)).findById(999L);
}

2.2 测试POST/UPDATE/DELETE接口(如果有)

如果你的Controller有新增、修改或删除接口,咱们同样可以Mock Repository的save、deleteById等方法,然后验证请求和方法调用:

import com.fasterxml.jackson.databind.ObjectMapper;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.argThat;

@Test
public void testCreateProduct() throws Exception {
    // 准备要新增的商品数据
    Product newProduct = new Product();
    newProduct.setName("New Bluetooth Speaker");
    newProduct.setPrice(89.99);

    // Stub save方法,返回带ID的保存后对象
    Product savedProduct = new Product();
    savedProduct.setId(2L);
    savedProduct.setName("New Bluetooth Speaker");
    savedProduct.setPrice(89.99);
    when(productRepo.save(argThat(product -> 
            product.getName().equals("New Bluetooth Speaker") && product.getPrice() == 89.99)))
            .thenReturn(savedProduct);

    // 发送POST请求
    mvc.perform(post("/api/products")
            .content(new ObjectMapper().writeValueAsString(newProduct))
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value(2L));

    // 验证save方法被调用了一次
    verify(productRepo, Mockito.times(1)).save(Mockito.any(Product.class));
}

3. 关键注意点

  • @MockBean的作用:它会自动替换Spring容器中的真实Repository Bean,所以你的测试完全不会触达真实数据库,所有数据源交互都是Mock的。
  • @Before的作用:这个注解的方法会在每个测试方法执行前运行,保证每个测试都有干净的Mock状态,不会互相干扰。
  • Mock重置:默认情况下,每个测试方法都会重新创建@MockBean对象,所以大部分时候不需要手动重置。如果需要,可以在setupMock开头加上Mockito.reset(productRepo, monitorRepo);。
  • 验证方法调用:除了times(1),还可以用never()(方法从未被调用)、atLeastOnce()(至少被调用一次)等匹配器,灵活验证交互逻辑。

内容的提问来源于stack exchange,提问作者Evgeni Dimitrov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:02:53