使用 JUnit 4在 Spring 中进行单元测试的完整步骤_spring-test 4.x + junit 4
以下是使用 JUnit 4 在 Spring 中进行单元测试的完整步骤,包含配置、核心注解、测试场景及代码示例:
1. 添加依赖
在 pom.xml
中引入必要的测试依赖(以 Spring 4/5 + JUnit 4 为例):
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.23</version> <scope>test</scope></dependency><dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.5.1</version> <scope>test</scope></dependency><dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.1.214</version> <scope>test</scope></dependency>
2. 编写单元测试的核心步骤
步骤 1:测试 Service 层(模拟 DAO 依赖)
使用 SpringJUnit4ClassRunner
加载 Spring 上下文,并通过 @Autowired
注入被测对象。
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\", \"alice@example.com\"); // 定义模拟行为:当调用 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 内存数据库。
@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\", \"bob@example.com\"); userDao.insertUser(user); // 查询并断言 User foundUser = userDao.findUserById(user.getId()); assertNotNull(foundUser); assertEquals(\"Bob\", foundUser.getName()); }}
步骤 3:测试事务回滚
在 DAO 层测试中,使用 @Transactional
确保测试后数据回滚。
@Test@Transactionalpublic void testCreateUserWithRollback() { User user = new User(\"Charlie\", \"charlie@example.com\"); userDao.insertUser(user); // 测试结束后事务自动回滚,数据库无新增记录}
步骤 4:测试 REST Controller 层
使用 MockMvc
模拟 HTTP 请求(需手动配置)。
@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)
@ContextConfiguration
@Autowired
@Transactional
@Before
@BeforeEach
)。@Mock
MockitoAnnotations.initMocks(this)
初始化)。4. 测试场景与最佳实践
场景 1:隔离依赖(Mocking)
- 目标:测试 Service 层逻辑时,避免依赖真实数据库。
- 工具:使用 Mockito 模拟 DAO 层。
场景 2:轻量级集成测试
- 目标:测试 DAO 层 SQL 是否正确,使用 H2 内存数据库。
- 工具:
@Transactional
+ H2。
场景 3:全栈测试
- 目标:验证 Controller → Service → DAO 的完整流程。
- 工具:
MockMvc
+ Spring 上下文。
最佳实践
- 测试命名:清晰表达测试目的(如
methodUnderTest_Scenario_ExpectedResult
)。 - 测试覆盖率:覆盖正常流程、异常分支和边界条件。
- 事务管理:在 DAO 层测试中使用
@Transactional
避免数据残留。 - 模拟依赖:使用 Mockito 隔离外部服务(如 HTTP 调用、第三方 API)。
5. 示例:测试异常场景
@Test(expected = DataAccessException.class)public void testDatabaseError() { User user = new User(\"David\", \"david@example.com\"); // 模拟数据库抛出异常 when(userDao.insertUser(user)).thenThrow(new DataAccessException(\"Connection failed\") {}); userService.createUser(user);}
6. JUnit 4 与 JUnit 5 的对比
@Before
, @After
@BeforeEach
, @AfterEach
@Parameters
+ 外部数据源@ParameterizedTest
@DynamicTest
@RunWith
@ExtendWith
总结
- JUnit 4 适用场景:维护旧项目、兼容 Java 7 或需要与旧版 Spring 框架集成。
- JUnit 5 优势:更简洁的语法、更强大的功能(如参数化测试)、更好的模块化支持。
建议:
- 新项目优先使用 JUnit 5。
- 旧项目可逐步迁移,或通过
junit-vintage-engine
兼容 JUnit 4 和 5 的测试。