文章目录
- [MyBatis-Plus 单元测试中 Lambda Mock 的坑与解决](#MyBatis-Plus 单元测试中 Lambda Mock 的坑与解决)
-
- 一、问题现象:一个很"莫名其妙"的异常
- 二、先给结论(重要)
- [三、问题根源:Lambda 缓存 + Mock = 翻车现场](#三、问题根源:Lambda 缓存 + Mock = 翻车现场)
-
- [1️⃣ MyBatis-Plus 的 Lambda 是"有状态"的](#1️⃣ MyBatis-Plus 的 Lambda 是“有状态”的)
- [2️⃣ 单元测试里的 Mapper 是"假的"](#2️⃣ 单元测试里的 Mapper 是“假的”)
- [四、最小 Demo:错误 vs 正确](#四、最小 Demo:错误 vs 正确)
-
- [❌ 错误示例(非常常见)(高概率翻车)](#❌ 错误示例(非常常见)(高概率翻车))
- [✅ 实测最稳方案(推荐)](#✅ 实测最稳方案(推荐))
- [五、一个重要认知:单元测试不该关心 SQL 细节](#五、一个重要认知:单元测试不该关心 SQL 细节)
-
- [❓ 单元测试应该测什么?](#❓ 单元测试应该测什么?)
- [❌ 不应该在单元测试里测什么?](#❌ 不应该在单元测试里测什么?)
- [六、如果"我就是想用 Lambda",怎么办?](#六、如果“我就是想用 Lambda”,怎么办?)
-
- [方案:手动触发 Lambda 缓存初始化](#方案:手动触发 Lambda 缓存初始化)
- 七、真实项目中的推荐测试分层
- 八、顺手避坑:UnnecessaryStubbingException
-
- [❌ 常见反例](#❌ 常见反例)
- [✅ 正确做法:按需 Mock](#✅ 正确做法:按需 Mock)
- [九、总结(请记住这 3 句话)](#九、总结(请记住这 3 句话))
MyBatis-Plus 单元测试中 Lambda Mock 的坑与解决
核心主题 :
单元测试里 Mock MyBatis-Plus Mapper 时,为什么一用
LambdaQueryWrapper / LambdaUpdateWrapper就报错?以及:如何用最简单、最稳妥的方式解决它。
一、问题现象:一个很"莫名其妙"的异常
很多人在写 MyBatis-Plus 单元测试时,都见过下面这个异常:
text
MybatisPlusException:
can not find lambda cache for this entity [com.example.domain.User]
它通常出现在:
- 使用
LambdaQueryWrapper - 使用
LambdaUpdateWrapper - Mapper 被 Mockito
@Mock或@MockBean
而线上环境完全没问题,只有单元测试会炸。
这类问题非常典型,也非常让人困惑。
二、先给结论(重要)
99% 的场景下,这不是你业务代码的问题,而是 Mock 方式不当。
最稳妥、最推荐的做法只有一句:⚠️ 折中方案(不完全可靠)
java
when(userMapper.update(any(), any())).thenReturn(1);
- 在很多项目中可用
- 但在某些 MyBatis-Plus 版本 / 复杂测试场景下
- 仍可能失败
而不是:
java
// ❌ 非常容易触发 Lambda cache 异常
when(userMapper.update(any(User.class), any(LambdaUpdateWrapper.class))).thenReturn(1);
下面我们一步一步解释:为什么会这样,以及为什么 any() 能救命。
三、问题根源:Lambda 缓存 + Mock = 翻车现场
1️⃣ MyBatis-Plus 的 Lambda 是"有状态"的
MyBatis-Plus 的 Lambda 写法并不是语法糖,它背后依赖一个 Lambda 缓存:
- 通过
User::getId解析出字段名id - 解析结果会缓存在内部
LambdaCache - 这个缓存和 实体类的 Class、类加载器、初始化时机 强相关
一句话总结:
LambdaWrapper 不是一个普通 POJO,它在构造时就依赖 MyBatis-Plus 的运行环境。
2️⃣ 单元测试里的 Mapper 是"假的"
在单元测试中我们通常这样写:
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserMapper userMapper;
}
此时:
- Mapper 是 Mockito 生成的代理对象
- 不存在真实的 MyBatis 上下文
- Lambda 缓存可能根本没初始化
但你却在 Mock 时强行写:
java
any(LambdaUpdateWrapper.class)
👉 Mockito 会尝试去解析这个参数类型
👉 MyBatis-Plus 会尝试读取 Lambda 缓存
👉 然后发现:缓存不存在
于是,异常就出现了。
四、最小 Demo:错误 vs 正确
❌ 错误示例(非常常见)(高概率翻车)
java
@Test
void testDeleteUser() {
when(userMapper.update(any(User.class), any(LambdaUpdateWrapper.class)))
.thenReturn(1);
boolean result = userService.deleteUser(1L);
assertTrue(result);
}
问题点只有一个:
你在单元测试里,强依赖了
LambdaUpdateWrapper的内部机制。
✅ 实测最稳方案(推荐)
单元测试中:彻底避免 LambdaWrapper。
业务代码调整(关键)
java
// ❌ 原写法
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, userId);
// ✅ 推荐写法
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("id", userId);
对应单元测试
java
@Test
@DisplayName("删除用户 - 成功")
void testDeleteUser() {
when(userMapper.update(any(), any()))
.thenReturn(1);
boolean result = userService.deleteUser(1L);
assertTrue(result);
}
为什么它最稳?
QueryWrapper / UpdateWrapper是普通对象- 不依赖 Lambda 缓存
- 不依赖实体元数据初始化
- 与 Mockito / 测试环境完全解耦
👉 *这是目前实践中稳定性最高、成本最低的方案。
五、一个重要认知:单元测试不该关心 SQL 细节
很多 Lambda Mock 问题,本质是测试关注点放错了。
❓ 单元测试应该测什么?
- 业务逻辑是否正确
- 条件成立时是否调用了 Mapper
- 返回值是否被正确处理
❌ 不应该在单元测试里测什么?
- LambdaWrapper 拼了哪些字段
- SQL 是否正确
- 条件构造是否符合预期
👉 这些是集成测试 / Mapper 测试的职责。
六、如果"我就是想用 Lambda",怎么办?
有,但不推荐。
方案:手动触发 Lambda 缓存初始化
java
@Test
void testWithLambdaInit() {
// 触发一次 Lambda 解析
new LambdaQueryWrapper<User>().select(User::getId);
when(userMapper.selectList(any())).thenReturn(List.of());
List<User> users = userService.list();
assertNotNull(users);
}
缺点非常明显:
- 测试代码变脏
- 可读性下降
- 仍可能受类加载器影响
👉 只在极少数必须验证 Lambda 行为的场景下使用。
七、真实项目中的推荐测试分层
text
单元测试(Service)
- Mock Mapper
- 一律 any()
- 只测业务分支
集成测试(Mapper / DB)
- 真数据库 / Testcontainers
- 真 LambdaWrapper
- 验证 SQL 行为
不要试图在一个测试里干两件事。
八、顺手避坑:UnnecessaryStubbingException
❌ 常见反例
java
@BeforeEach
void setUp() {
when(cryptoKeyProvider.getMasterKey()).thenReturn(key);
}
但某些测试根本用不到它。
✅ 正确做法:按需 Mock
java
@Test
void testCreateUser() {
when(cryptoKeyProvider.getMasterKey()).thenReturn(key);
when(userMapper.insert(any())).thenReturn(1);
}
👉 Mock 写在测试里,而不是统一写在 @BeforeEach。
九、总结(请记住这 3 句话)
- LambdaWrapper 在单元测试里是"高危对象"
- Service 单测中,
any()是最优解 - SQL 正确性不属于单元测试的职责
如果你在单测里看到:
text
can not find lambda cache
别急着怀疑人生,先看看你是不是写了:
java
any(LambdaUpdateWrapper.class)
改成 any(),世界大概率就清净了。
