使用 JUnit 4在 Spring 中进行单元测试的完整步骤

以下是使用 JUnit 4 在 Spring 中进行单元测试的完整步骤,包含配置、核心注解、测试场景及代码示例:


1. 添加依赖

pom.xml 中引入必要的测试依赖(以 Spring 4/5 + JUnit 4 为例):

xml 复制代码
<!-- JUnit 4 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

<!-- Spring Test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.23</version>
    <scope>test</scope>
</dependency>

<!-- Mock 对象(Mockito) -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.5.1</version>
    <scope>test</scope>
</dependency>

<!-- 数据库测试(H2,用于 DAO 层测试) -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
    <scope>test</scope>
</dependency>

2. 编写单元测试的核心步骤

步骤 1:测试 Service 层(模拟 DAO 依赖)

使用 SpringJUnit4ClassRunner 加载 Spring 上下文,并通过 @Autowired 注入被测对象。

java 复制代码
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(SpringJUnit4ClassRunner.class) // 使用 Spring 的测试运行器
@ContextConfiguration(locations = {"classpath:applicationContext.xml"}) // 加载 Spring 配置文件
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Mock
    private UserDao userDao;

    // 手动注入模拟对象(JUnit 4 需手动初始化)
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        userService.setUserDao(userDao); // 将模拟对象注入 Service
    }

    @Test
    public void testCreateUser_Success() {
        User user = new User("Alice", "[email protected]");
        
        // 定义模拟行为:当调用 userDao.insertUser() 时返回 true
        when(userDao.insertUser(user)).thenReturn(true);
        
        // 调用被测方法
        boolean result = userService.createUser(user);
        
        // 断言结果
        assertTrue(result);
        verify(userDao, times(1)).insertUser(user);
    }

    @Test
    public void testGetUserById_NotFound() {
        int userId = 999;
        when(userDao.findUserById(userId)).thenReturn(null);
        
        // 断言抛出异常
        try {
            userService.getUserById(userId);
            fail("Expected UserNotFoundException");
        } catch (UserNotFoundException e) {
            assertEquals("User not found", e.getMessage());
        }
    }
}

步骤 2:测试 DAO 层(使用内存数据库 H2)

通过 @RunWith(SpringJUnit4ClassRunner.class) 加载 Spring 上下文,并使用 H2 内存数据库。

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class UserDaoIntegrationTest {

    @Autowired
    private UserDao userDao;

    @Test
    @Transactional // 自动回滚事务,避免污染数据库
    public void testFindUserById() {
        // 插入测试数据
        User user = new User("Bob", "[email protected]");
        userDao.insertUser(user);
        
        // 查询并断言
        User foundUser = userDao.findUserById(user.getId());
        assertNotNull(foundUser);
        assertEquals("Bob", foundUser.getName());
    }
}

步骤 3:测试事务回滚

在 DAO 层测试中,使用 @Transactional 确保测试后数据回滚。

java 复制代码
@Test
@Transactional
public void testCreateUserWithRollback() {
    User user = new User("Charlie", "[email protected]");
    userDao.insertUser(user);
    
    // 测试结束后事务自动回滚,数据库无新增记录
}

步骤 4:测试 REST Controller 层

使用 MockMvc 模拟 HTTP 请求(需手动配置)。

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebConfig.class}) // 加载 Web 配置类
@WebAppConfiguration // 启用 Web 应用上下文
public class UserControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void testGetUserById_Success() throws Exception {
        mockMvc.perform(get("/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("Alice"));
    }
}

3. 关键注解说明

注解 用途
@RunWith(SpringJUnit4ClassRunner.class) 启用 Spring 的测试运行器,加载应用上下文。
@ContextConfiguration 指定 Spring 配置文件或配置类。
@Autowired 自动注入 Spring 管理的 Bean。
@Transactional 为测试方法启用事务,测试结束后自动回滚。
@Before 在每个测试方法前执行(替代 JUnit 5 的 @BeforeEach)。
@Mock 创建 Mockito 模拟对象(需配合 MockitoAnnotations.initMocks(this) 初始化)。

4. 测试场景与最佳实践

场景 1:隔离依赖(Mocking)
  • 目标:测试 Service 层逻辑时,避免依赖真实数据库。
  • 工具:使用 Mockito 模拟 DAO 层。
场景 2:轻量级集成测试
  • 目标:测试 DAO 层 SQL 是否正确,使用 H2 内存数据库。
  • 工具@Transactional + H2。
场景 3:全栈测试
  • 目标:验证 Controller → Service → DAO 的完整流程。
  • 工具MockMvc + Spring 上下文。
最佳实践
  1. 测试命名 :清晰表达测试目的(如 methodUnderTest_Scenario_ExpectedResult)。
  2. 测试覆盖率:覆盖正常流程、异常分支和边界条件。
  3. 事务管理 :在 DAO 层测试中使用 @Transactional 避免数据残留。
  4. 模拟依赖:使用 Mockito 隔离外部服务(如 HTTP 调用、第三方 API)。

5. 示例:测试异常场景

java 复制代码
@Test(expected = DataAccessException.class)
public void testDatabaseError() {
    User user = new User("David", "[email protected]");
    // 模拟数据库抛出异常
    when(userDao.insertUser(user)).thenThrow(new DataAccessException("Connection failed") {});
    
    userService.createUser(user);
}

6. JUnit 4 与 JUnit 5 的对比

特性 JUnit 4 JUnit 5
注解 @Before, @After @BeforeEach, @AfterEach
参数化测试 需使用 @Parameters + 外部数据源 原生支持 @ParameterizedTest
动态测试 不支持 支持 @DynamicTest
扩展模型 依赖 Spring 的 @RunWith 基于 JPMS 的 @ExtendWith
Java 版本要求 Java 5+ Java 8+

总结

  • JUnit 4 适用场景:维护旧项目、兼容 Java 7 或需要与旧版 Spring 框架集成。
  • JUnit 5 优势:更简洁的语法、更强大的功能(如参数化测试)、更好的模块化支持。

建议

  • 新项目优先使用 JUnit 5。
  • 旧项目可逐步迁移,或通过 junit-vintage-engine 兼容 JUnit 4 和 5 的测试。
相关推荐
Code哈哈笑1 小时前
【Spring Boot】深入解析:#{} 和 ${}
java·spring boot·后端·spring·mybatis
云之兕1 小时前
在面试中被问到spring是什么?
spring·面试·职场和发展
brevity_souls1 小时前
Spring Security
java·mysql·spring
优雅的落幕12 小时前
Spring--统一数据返回格式与统一异常处理
java·spring·状态模式
程序员阿鹏13 小时前
实现SpringBoot底层机制【Tomcat启动分析+Spring容器初始化+Tomcat 如何关联 Spring容器】
java·spring boot·后端·spring·docker·tomcat·intellij-idea
小可爱的大笨蛋15 小时前
Spring AI - Redis缓存对话
java·spring·spring ai
等什么君!15 小时前
springmvc入门案例
后端·spring
hello_ejb316 小时前
聊聊Spring AI Alibaba的ObsidianDocumentReader
java·人工智能·spring
bing_15816 小时前
一个 HTTP 请求进入 Spring MVC 应用后,大致经历了哪些主要步骤?
spring·http·mvc