如何在Spring测试上下文注入@Repository与多个@Service
我基于Spring开发REST接口,要单独测试包含mapper、repository和验证逻辑的业务层,做不含REST部分的胖集成测试/系统测试。我拒绝使用Mock,因为Mock的行为是自定义的,无法反映待测试代码的真实情况,可能和业务代码一样存在bug;只在无法通过输入触发行为时才考虑Mock。
目前遇到的问题是测试中组件组装困难:现有示例要么是仅聚焦repository的@DataJpaTest,要么是Controller/系统测试中Mock底层逻辑。我需要能灵活组合测试组件(可选择Mock或不Mock部分组件),但Spring测试加载的上下文不全,导致无法找到所需组件,运行测试时报错。
业务代码
// 业务逻辑实现类 public class IAMBusinessLogicImplementation implements IAMBusinessLogicInterface { private final UserRepositoryInterface _userRepository; private final UserMapperInterface _userMappers; private final IAMValidatorInterface _validator; public IAMBusinessLogicImplementation( @NonNull UserRepositoryInterface _userRepository, @NonNull UserMapperInterface _userMappers, @NonNull IAMValidatorInterface _validator) { this._userRepository = _userRepository; this._userMappers = _userMappers; this._validator = _validator; } public UserDto createUser(@NonNull UserDto userDto) { // 业务逻辑实现 } } // 验证器实现类 public class IAMValidatorImplementation implements IAMValidatorInterface { } // Mapper实现类 public class UserMapperImplementation implements UserMapperInterface { } // Repository接口 public interface UserRepositoryInterface extends CrudRepository<User, Long> { }
测试代码
@SpringBootTest(classes = {IAMBusinessLogicImplementation.class}) @EnableJpaRepositories(basePackages = "com.encyclopediagalactica.iam.tests.repository") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @AutoConfigureDataJpa @EntityScan("com.encyclopediagalactica.iam.entities") @ComponentScan() public class IAMBusinessLogicTestsBase { @Autowired public IAMBusinessLogicInterface _sut; @Test public void createUser() { // Arrange UserDto userDto = UserDto.builder() .firstName("asd") .lastName("dsa") .build(); // Act UserDto result = _sut.createUser(userDto); // Assert assertThat(result).isNotNull(); } }
错误日志
2023-04-23T21:28:25.495+02:00 WARN 37577 --- [ main]
o.s.w.c.s.GenericWebApplicationContext : Exception encountered
during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'IAMBusinessLogicImplementation':
Unsatisfied dependency expressed through constructor parameter 0: No
qualifying bean of type
'com.encyclopediagalactica.iam.repositories.UserRepositoryInterface'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations: {}
错误核心:Spring无法找到UserRepositoryInterface类型的Bean,无法满足IAMBusinessLogicImplementation的构造函数依赖。
解决方案
1. 修正Repository扫描路径
@EnableJpaRepositories的basePackages配置错误,应该指向业务代码中Repository所在的包,而非测试包:
@EnableJpaRepositories(basePackages = "com.encyclopediagalactica.iam.repositories")
2. 让业务组件被Spring容器管理
业务逻辑类、验证器、Mapper都需要添加Spring组件注解(@Service、@Component等),否则Spring无法扫描生成Bean:
@Service public class IAMBusinessLogicImplementation implements IAMBusinessLogicInterface { // 原有代码不变 } @Component public class IAMValidatorImplementation implements IAMValidatorInterface { } @Component public class UserMapperImplementation implements UserMapperInterface { }
3. 优化测试上下文配置
- 建议去掉
@SpringBootTest的classes参数,默认加载带@SpringBootApplication的主配置类,这样能自动扫描所有必要组件,避免遗漏依赖; - 去掉不必要的
@ComponentScan(),如果需要指定扫描范围,明确写出业务代码的根包:
@SpringBootTest @EnableJpaRepositories(basePackages = "com.encyclopediagalactica.iam.repositories") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @EntityScan("com.encyclopediagalactica.iam.entities") public class IAMBusinessLogicTestsBase { // 原有测试代码不变 }
4. 按需Mock组件(灵活组合)
如果需要Mock某部分组件(比如外部依赖无法在测试环境运行),用@MockBean替换真实Bean,不影响其他组件的真实实例:
@SpringBootTest @EnableJpaRepositories(basePackages = "com.encyclopediagalactica.iam.repositories") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @EntityScan("com.encyclopediagalactica.iam.entities") public class IAMBusinessLogicTestsBase { @Autowired public IAMBusinessLogicInterface _sut; // Mock验证器,保留Repository和Mapper的真实实例 @MockBean private IAMValidatorInterface _validator; @Test public void createUser() { // Arrange UserDto userDto = UserDto.builder() .firstName("asd") .lastName("dsa") .build(); // 定义Mock行为 Mockito.when(_validator.validateUser(any(UserDto.class))).thenReturn(true); // Act UserDto result = _sut.createUser(userDto); // Assert assertThat(result).isNotNull(); } }
内容的提问来源于stack exchange,提问作者AndrasCsanyi

