SpringBoot集成测试模拟环境

wen java案例 5

本文目录导读:

SpringBoot集成测试模拟环境

  1. 测试环境搭建依赖
  2. 测试模拟方案
  3. 数据库模拟
  4. 外部服务模拟
  5. Spring Cloud组件模拟
  6. 测试最佳实践
  7. 性能考虑

我来详细介绍SpringBoot集成测试的模拟环境搭建和常用方案。

测试环境搭建依赖

Maven依赖配置

<!-- Spring Boot Test -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <scope>test</scope>
</dependency>
<!-- 如果需要Web环境 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <scope>test</scope>
</dependency>
<!-- 数据库测试内存库 -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>
<!-- Testcontainers (容器化测试) -->
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>

测试模拟方案

Mockito模拟

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
    @Mock
    private UserRepository userRepository;
    @InjectMocks
    private UserService userService;
    @Test
    void testFindUserById() {
        // 模拟数据
        User mockUser = new User(1L, "张三", "zhangsan@test.com");
        Mockito.when(userRepository.findById(1L))
               .thenReturn(Optional.of(mockUser));
        // 执行测试
        User result = userService.findUserById(1L);
        // 验证结果
        assertEquals("张三", result.getName());
        Mockito.verify(userRepository).findById(1L);
    }
}

Spring Boot集成测试

@SpringBootTest
@AutoConfigureMockMvc
@Transactional  // 测试后自动回滚
public class UserControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private UserRepository userRepository;
    @Test
    void testCreateUser() throws Exception {
        // 准备测试数据
        User user = new User();
        user.setName("测试用户");
        user.setEmail("test@example.com");
        // 执行请求
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(asJsonString(user)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.name").value("测试用户"));
    }
    private String asJsonString(Object obj) {
        try {
            return new ObjectMapper().writeValueAsString(obj);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

切片测试

// Web层测试
@WebMvcTest(UserController.class)
public class UserControllerSliceTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private UserService userService;
    @Test
    void testGetUser() throws Exception {
        // 模拟Service层
        Mockito.when(userService.findUserById(1L))
               .thenReturn(new User(1L, "张三", "test@test.com"));
        mockMvc.perform(get("/api/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("张三"));
    }
}
// 数据层测试
@DataJpaTest
public class UserRepositoryTest {
    @Autowired
    private TestEntityManager entityManager;
    @Autowired
    private UserRepository userRepository;
    @Test
    void testFindByEmail() {
        // 准备数据
        User user = new User("张三", "test@test.com");
        entityManager.persist(user);
        entityManager.flush();
        // 执行查询
        Optional<User> found = userRepository.findByEmail("test@test.com");
        // 验证结果
        assertTrue(found.isPresent());
        assertEquals("张三", found.get().getName());
    }
}

数据库模拟

H2内存数据库

# application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  jpa:
    hibernate:
      ddl-auto: create-drop
    database-platform: org.hibernate.dialect.H2Dialect
  h2:
    console:
      enabled: true

Testcontainers容器化测试

@SpringBootTest
@Testcontainers
public class DatabaseContainerTest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:14")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");
    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }
    @Autowired
    private UserRepository userRepository;
    @Test
    void testDatabaseInteraction() {
        // 测试实际的数据库操作
        User user = new User("张三", "test@test.com");
        userRepository.save(user);
        assertNotNull(user.getId());
    }
}

外部服务模拟

Redis模拟

@SpringBootTest
@Testcontainers
public class RedisTest {
    @Container
    static GenericContainer<?> redis = new GenericContainer<>("redis:6-alpine")
            .withExposedPorts(6379);
    @DynamicPropertySource
    static void configureRedis(DynamicPropertyRegistry registry) {
        registry.add("spring.redis.host", redis::getHost);
        registry.add("spring.redis.port", redis::getFirstMappedPort);
    }
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Test
    void testRedisOperations() {
        redisTemplate.opsForValue().set("key", "value");
        assertEquals("value", redisTemplate.opsForValue().get("key"));
    }
}

外部API Mock

@SpringBootTest
@AutoConfigureMockMvc
public class ExternalApiTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private ExternalApiService externalApiService;
    @Test
    void testExternalCall() throws Exception {
        // 模拟外部API响应
        Mockito.when(externalApiService.callExternalApi())
               .thenReturn(new ExternalResponse("success", 200));
        mockMvc.perform(post("/api/process")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"data\":\"test\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("success"));
    }
}

WireMock模拟HTTP服务

@SpringBootTest
@WireMockTest(httpPort = 8089)
public class WireMockTest {
    @Test
    void testExternalHttpCall() {
        // 配置WireMock响应
        stubFor(get(urlEqualTo("/api/data"))
                .willReturn(aResponse()
                    .withHeader("Content-Type", "application/json")
                    .withBody("{\"id\":1, \"name\":\"test\"}")));
        // 测试代码
        ResponseEntity<String> response = restTemplate.getForEntity(
            "http://localhost:8089/api/data", String.class);
        assertEquals(HttpStatus.OK, response.getStatusCode());
    }
}

Spring Cloud组件模拟

配置中心模拟

@SpringBootTest
@EnableConfigurationProperties
@ContextConfiguration(initializers = ConfigDataInitializer.class)
public class ConfigTest {
    @Value("${test.property}")
    private String testProperty;
    @Test
    void testConfigProperty() {
        assertEquals("test-value", testProperty);
    }
}

服务发现模拟

@SpringBootTest
@AutoConfigureMockMvc
public class ServiceDiscoveryTest {
    @MockBean
    private DiscoveryClient discoveryClient;
    @Test
    void testServiceDiscovery() {
        // 模拟服务发现
        List<ServiceInstance> instances = Arrays.asList(
            new DefaultServiceInstance("service1", "user-service", "localhost", 8080, false)
        );
        Mockito.when(discoveryClient.getInstances("user-service"))
               .thenReturn(instances);
        // 执行测试
        List<ServiceInstance> result = discoveryClient.getInstances("user-service");
        assertEquals(1, result.size());
    }
}

测试最佳实践

测试配置隔离

@TestConfiguration
public class TestConfig {
    @Bean
    @Primary
    public DataSource testDataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
    }
    @Bean
    @Primary
    public RestTemplate testRestTemplate() {
        return new RestTemplate();
    }
}

测试分类和过滤

// 定义测试分类
@Tag("integration")
@Tag("database")
public class DatabaseIntegrationTest {
    // 测试代码
}
// 运行特定标签的测试
// mvn test -Dgroups="integration"

测试前后置处理

@SpringBootTest
public class TestLifecycleTest {
    @BeforeAll
    static void initAll() {
        // 全局初始化
    }
    @BeforeEach
    void init() {
        // 每个测试方法前执行
    }
    @Test
    void testSomething() {
        // 测试逻辑
    }
    @AfterEach
    void tearDown() {
        // 每个测试方法后执行
    }
    @AfterAll
    static void tearDownAll() {
        // 全局清理
    }
}

性能考虑

  1. 测试范围选择:控制测试范围,避免不必要的上下文加载
  2. 并行测试:合理配置并行执行策略
  3. 资源清理:确保测试后资源被正确释放
  4. 依赖管理:管理好测试环境的依赖版本

这个测试模拟方案可以帮助你在开发过程中快速验证业务逻辑,同时减少对外部环境的依赖,根据项目实际需求,选择合适的测试方案组合。

抱歉,评论功能暂时关闭!