You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 20:35:10