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

Spring Boot中自制JPA Repository Mock实现与测试方案咨询

不借助Mock框架自制Repository Mock测试Spring Service

我太懂你不想依赖Mockito、EasyMock这些框架的想法了——其实完全可以通过手动实现接口的方式自制Mock,完全不用第三方工具。结合你给出的代码,我给你一步步拆解可行方案:

核心思路

因为CLASSrepo是继承自JpaRepository的接口,我们只需要手动写一个该接口的实现类,用内存集合模拟数据库的存储和操作,完全自己控制返回结果、业务逻辑甚至异常场景。


第一步:编写Mock Repository实现类

这个类会完全模拟数据库的CRUD操作,用内存List代替真实数据库:

// 手动实现CLASSrepo接口,作为Mock Repository
public class MockCLASSrepo implements CLASSrepo {
    // 用内存List模拟数据库表
    private final List<CLASS> mockDatabase = new ArrayList<>();
    private long nextAutoIncrementId = 1;

    // 模拟根据ID查询
    @Override
    public Optional<CLASS> findById(Long id) {
        return mockDatabase.stream()
                .filter(clazz -> clazz.getId().equals(id))
                .findFirst();
    }

    // 模拟保存/更新操作
    @Override
    public <S extends CLASS> S save(S entity) {
        if (entity.getId() == null) {
            // 模拟自增ID
            entity.setId(nextAutoIncrementId++);
            mockDatabase.add(entity);
        } else {
            // 模拟更新逻辑:先删后加
            mockDatabase.removeIf(clazz -> clazz.getId().equals(entity.getId()));
            mockDatabase.add(entity);
        }
        return entity;
    }

    // 模拟查询所有
    @Override
    public List<CLASS> findAll() {
        // 返回副本避免外部直接修改内存数据
        return new ArrayList<>(mockDatabase);
    }

    // 模拟删除操作
    @Override
    public void deleteById(Long id) {
        mockDatabase.removeIf(clazz -> clazz.getId().equals(id));
    }

    // 其他未用到的JpaRepository方法,可以直接抛异常或留空
    @Override
    public long count() {
        throw new UnsupportedOperationException("本Mock未实现count方法");
    }

    // 剩下的JpaRepository方法同理,根据你的Service实际使用情况实现即可
}

第二步:优化Service的注入方式(可选但强烈推荐)

原来的Service用字段注入@Autowired不利于测试,改成构造器注入(Spring 4.3+可以不用加@Autowired),这样测试时能直接传入Mock对象:

@Service
public class CLASSservice {
    private final CLASSrepo classRepo;

    // 构造器注入,替代字段上的@Autowired
    public CLASSservice(CLASSrepo classRepo) {
        this.classRepo = classRepo;
    }

    // 你的业务方法示例
    public CLASS saveClass(CLASS clazz) {
        return classRepo.save(clazz);
    }

    public Optional<CLASS> getClassById(Long id) {
        return classRepo.findById(id);
    }

    public void deleteClass(Long id) {
        classRepo.deleteById(id);
    }
}

如果不想修改原有Service,也可以给classRepo加一个setter方法,测试时通过setter注入Mock。


第三步:编写测试类,手动注入Mock

完全不用Spring Test的@MockBean或任何框架,直接手动实例化Mock和Service:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class CLASSserviceTest {

    @Test
    void testSaveAndRetrieveClass() {
        // 1. 实例化自制的Mock Repository
        MockCLASSrepo mockRepo = new MockCLASSrepo();
        // 2. 把MockRepo注入到Service(构造器注入直接传参)
        CLASSservice service = new CLASSservice(mockRepo);

        // 3. 测试业务逻辑
        CLASS testClass = new CLASS();
        testClass.setName("测试班级"); // 假设CLASS有name字段

        // 保存测试数据
        CLASS savedClass = service.saveClass(testClass);
        assertNotNull(savedClass.getId());
        assertEquals("测试班级", savedClass.getName());

        // 根据ID查询
        Optional<CLASS> foundClass = service.getClassById(savedClass.getId());
        assertTrue(foundClass.isPresent());
        assertEquals(savedClass.getId(), foundClass.get().getId());
    }

    @Test
    void testDeleteClass() {
        MockCLASSrepo mockRepo = new MockCLASSrepo();
        CLASSservice service = new CLASSservice(mockRepo);

        CLASS testClass = new CLASS();
        testClass.setName("待删除班级");
        CLASS saved = service.saveClass(testClass);
        Long classId = saved.getId();

        // 执行删除
        service.deleteClass(classId);
        // 验证删除结果
        Optional<CLASS> deletedClass = service.getClassById(classId);
        assertFalse(deletedClass.isPresent());
    }
}

额外技巧:模拟异常场景

如果需要测试Service的异常处理逻辑,直接在MockRepo里模拟异常即可:

// 在MockCLASSrepo的save方法中添加异常逻辑
@Override
public <S extends CLASS> S save(S entity) {
    if (entity.getId() != null && entity.getId().equals(-1L)) {
        // 模拟数据库保存失败的异常
        throw new RuntimeException("模拟数据库连接失败");
    }
    // 正常保存逻辑...
}

对应的测试用例:

@Test
void testSaveWithException() {
    MockCLASSrepo mockRepo = new MockCLASSrepo();
    CLASSservice service = new CLASSservice(mockRepo);

    CLASS errorClass = new CLASS();
    errorClass.setId(-1L);
    errorClass.setName("异常测试班级");

    // 验证Service是否正确处理了异常
    assertThrows(RuntimeException.class, () -> service.saveClass(errorClass));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:06:57