????????單元測試是軟件開發中至關重要的一環,Spring Boot 提供了強大的測試支持。以下是 Spring Boot 單元測試的詳細教程。
1. 準備工作
1.1 添加測試依賴
在?pom.xml
?中添加測試相關依賴:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency><!-- 如果需要MockMvc -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><scope>test</scope>
</dependency><!-- 如果需要AssertJ -->
<dependency><groupId>org.assertj</groupId><artifactId>assertj-core</artifactId><version>3.24.2</version><scope>test</scope>
</dependency>
1.2 測試類基本結構
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class MyApplicationTests {@Testpublic void contextLoads() {// 測試Spring上下文是否正常加載}
}
2. 不同類型的測試
2.1 服務層測試
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;@ExtendWith(MockitoExtension.class)
public class UserServiceTest {@Mockprivate UserRepository userRepository;@InjectMocksprivate UserService userService;@Testpublic void testGetUserById() {// 準備測試數據User mockUser = new User(1L, "test@example.com", "Test User");// 定義mock行為when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));// 調用測試方法User result = userService.getUserById(1L);// 驗證結果assertEquals("Test User", result.getName());verify(userRepository, times(1)).findById(1L);}
}
2.2 控制器層測試
使用MockMvc
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;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {@Autowiredprivate MockMvc mockMvc;@Testpublic void testGetUser() throws Exception {mockMvc.perform(get("/api/users/1")).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Test User"));}
}
使用WebTestClient (WebFlux)
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;@SpringBootTest
@AutoConfigureWebTestClient
public class UserControllerWebTestClientTest {@Autowiredprivate WebTestClient webTestClient;@Testpublic void testGetUser() {webTestClient.get().uri("/api/users/1").exchange().expectStatus().isOk().expectBody().jsonPath("$.name").isEqualTo("Test User");}
}
2.3 數據庫測試
使用@DataJpaTest
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import static org.assertj.core.api.Assertions.*;@DataJpaTest
public class UserRepositoryTest {@Autowiredprivate TestEntityManager entityManager;@Autowiredprivate UserRepository userRepository;@Testpublic void testFindByEmail() {// 準備測試數據User user = new User("test@example.com", "Test User");entityManager.persist(user);entityManager.flush();// 調用測試方法User found = userRepository.findByEmail(user.getEmail());// 驗證結果assertThat(found.getEmail()).isEqualTo(user.getEmail());}
}
使用@SpringBootTest + 測試數據庫
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
@ActiveProfiles("test")
@Transactional
public class UserServiceIntegrationTest {@Autowiredprivate UserService userService;@Autowiredprivate UserRepository userRepository;@Testpublic void testCreateUser() {User newUser = new User("new@example.com", "New User");User savedUser = userService.createUser(newUser);assertNotNull(savedUser.getId());assertEquals("New User", savedUser.getName());User found = userRepository.findById(savedUser.getId()).orElse(null);assertEquals("New User", found.getName());}
}
3. 常用測試技巧
3.1 參數化測試
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;public class ParameterizedTests {@ParameterizedTest@ValueSource(strings = {"racecar", "radar", "madam"})public void testPalindromes(String candidate) {assertTrue(StringUtils.isPalindrome(candidate));}
}
3.2 測試異常
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;public class ExceptionTest {@Testpublic void testException() {UserService userService = new UserService();assertThrows(UserNotFoundException.class, () -> {userService.getUserById(999L);});}
}
3.3 測試私有方法
雖然不推薦直接測試私有方法,但有時確實需要:
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;public class PrivateMethodTest {@Testpublic void testPrivateMethod() throws Exception {MyService service = new MyService();Method method = MyService.class.getDeclaredMethod("privateMethod", String.class);method.setAccessible(true);String result = (String) method.invoke(service, "input");assertEquals("expected", result);}
}
4. 測試配置
4.1 使用測試配置文件
創建?src/test/resources/application-test.properties
:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
然后在測試類上使用:
@ActiveProfiles("test")
4.2 使用測試切片
Spring Boot 提供了多種測試切片注解:
-
@WebMvcTest
?- 只測試MVC層 -
@DataJpaTest
?- 只測試JPA組件 -
@JsonTest
?- 只測試JSON序列化 -
@RestClientTest
?- 只測試REST客戶端@WebMvcTest(UserController.class) public class UserControllerSliceTest {@Autowiredprivate MockMvc mockMvc;@MockBeanprivate UserService userService;@Testpublic void testGetUser() throws Exception {when(userService.getUserById(1L)).thenReturn(new User(1L, "test@example.com", "Test User"));mockMvc.perform(get("/api/users/1")).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Test User"));} }
5. 測試最佳實踐
-
命名規范:測試方法名應清晰表達測試意圖,如?
shouldReturnUserWhenValidIdProvided()
-
單一職責:每個測試方法只測試一個功能點
-
AAA模式:遵循Arrange-Act-Assert模式組織測試代碼
-
避免依賴:測試之間不應有依賴關系
-
快速反饋:保持測試快速執行,避免I/O操作
-
覆蓋率:追求合理的測試覆蓋率,但不要盲目追求100%
-
Mock適度:不要過度使用mock,集成測試也很重要
6. 高級主題
6.1 自定義測試注解
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;import java.lang.annotation.*;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SpringBootTest
@ActiveProfiles("test")
public @interface MyIntegrationTest {
}
然后可以在測試類上使用?@MyIntegrationTest
?替代多個注解。
6.2 測試容器支持
使用Testcontainers進行集成測試:
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;@Testcontainers
@SpringBootTest
public class UserRepositoryTestContainersTest {@Containerpublic static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:13").withDatabaseName("testdb").withUsername("test").withPassword("test");@Testpublic void testWithRealDatabase() {// 測試代碼}
}
6.3 測試Spring Security
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.security.test.context.support.WithMockUser;
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.*;@SpringBootTest
@AutoConfigureMockMvc
public class SecuredControllerTest {@Autowiredprivate MockMvc mockMvc;@Test@WithMockUser(username="admin", roles={"ADMIN"})public void testAdminEndpoint() throws Exception {mockMvc.perform(get("/api/admin")).andExpect(status().isOk());}@Test@WithMockUser(username="user", roles={"USER"})public void testAdminEndpointForbidden() throws Exception {mockMvc.perform(get("/api/admin")).andExpect(status().isForbidden());}
}
7. 總結
Spring Boot 提供了全面的測試支持,從單元測試到集成測試,從Mock測試到真實環境測試。合理使用這些工具可以大大提高代碼質量和開發效率。
記住測試金字塔原則:多寫單元測試,適量集成測試,少量端到端測試。