使用@MockBean模拟Mapper的Spring Boot测试失败问题排查
Spring Boot中@MockBean模拟Mapper失效的解决方案
问题描述
开发Spring Boot应用时,尝试用@MockBean编写服务层测试以避免连接数据库。测试中模拟CartMapper的countCarts方法返回1,但断言失败,实际返回数据库真实数据5,说明@MockBean未生效。测试上下文配置类TestApplication带有@MapperScan注解,移除该注解后,又出现AddressMapper等Bean无法找到的错误,需要解决该问题。
测试代码
// CartServiceImpl中的countCarts方法 // @Override // public int countCarts(Cart cart) { // return cartMapper.countCarts(cart); // } @SpringBootTest(classes = TestApplication.class) @AutoConfigureMockMvc class CartServiceImplTest { @MockBean private CartMapper cartMapper; @Autowired private CartServiceImpl cartService; @Test void testCountCarts() { Cart cart = new Cart(); int count = 1; when(cartMapper.countCarts(Mockito.any())).thenReturn(count); assertEquals(count, cartService.countCarts(cart)); //verify(cartMapper).countCarts(cart); } }
断言失败错误信息
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 3.777 s <<< FAILURE! - in CartServiceImplTest testCountCarts Time elapsed: 0.809 s <<< FAILURE! org.opentest4j.AssertionFailedError: expected: <1> but was: <5> at CartServiceImplTest.testCountCarts(CartServiceImplTest.java:46)
测试上下文配置类
@ComponentScan(basePackages = {"world.xuewei"}, excludeFilters = {@ComponentScan.Filter(classes = {SpringBootApplication.class}) }) @SpringBootConfiguration @EnableAutoConfiguration @MapperScan(basePackages = {"world.xuewei.mapper"}) public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
移除@MapperScan后的错误信息
java.lang.IllegalStateException: Failed to load ApplicationContext Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'addressController': Unsatisfied dependency expressed through field 'addressService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'addressService': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'world.xuewei.mapper.AddressMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.annotation.Resource(shareable=true, lookup="", name="", description="", authenticationType=CONTAINER, type=java.lang.Object.class, mappedName="")}
解决方案
原因分析
@MapperScan会触发MyBatis生成真实的Mapper实例,而Spring中@MockBean的优先级低于MyBatis自动生成的Bean,导致真实的CartMapper被注入到CartServiceImpl中,Mock配置完全失效。
具体解决办法
办法一:排除数据库与MyBatis自动配置
在测试类的@SpringBootTest注解中,排除数据源和MyBatis相关的自动配置类,避免Spring初始化真实的数据库连接和Mapper实例:
@SpringBootTest(classes = TestApplication.class, exclude = { DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class }) @AutoConfigureMockMvc class CartServiceImplTest { // 原有测试代码不变 }
此时@MockBean的CartMapper会被正确注入,若其他Service依赖的Mapper也需要Mock,只需给对应的Mapper添加@MockBean即可。
办法二:给@MapperScan添加环境条件
修改TestApplication的配置,让@MapperScan仅在非测试环境生效:
@ComponentScan(basePackages = {"world.xuewei"}, excludeFilters = {@ComponentScan.Filter(classes = {SpringBootApplication.class}) }) @SpringBootConfiguration @EnableAutoConfiguration @Profile("!test") // 仅在非test环境启用Mapper扫描 @MapperScan(basePackages = {"world.xuewei.mapper"}) public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
然后在测试类中激活test环境:
@SpringBootTest(classes = TestApplication.class) @AutoConfigureMockMvc @ActiveProfiles("test") // 激活测试环境 class CartServiceImplTest { @MockBean private CartMapper cartMapper; @MockBean private AddressMapper addressMapper; // 其他需要的Mapper也Mock @Autowired private CartServiceImpl cartService; // 原有测试代码不变 }
办法三:脱离Spring上下文,用Mockito直接测试
无需加载完整的Spring上下文,改用Mockito的扩展类直接测试Service,速度更快且无环境冲突:
@ExtendWith(MockitoExtension.class) class CartServiceImplTest { @Mock // 模拟Mapper实例 private CartMapper cartMapper; @InjectMocks // 自动将Mock的Mapper注入到Service中 private CartServiceImpl cartService; @Test void testCountCarts() { Cart cart = new Cart(); int count = 1; when(cartMapper.countCarts(Mockito.any())).thenReturn(count); assertEquals(count, cartService.countCarts(cart)); verify(cartMapper).countCarts(cart); // 验证方法是否被调用 } }
内容的提问来源于stack exchange,提问作者Cillian
相关产品推荐
相关产品推荐

