使用JUnit编写单元测试时如何处理protected方法?
先把核心概念给你讲明白
1. 单元测试
就是针对单个代码单元(比如一个方法、一个类)的独立测试,目标是验证这个单元的逻辑是否符合预期,测试时要尽量把外部依赖(比如数据库、其他复杂服务)隔离开,只测当前单元的逻辑。
2. Mock对象
是用来替代真实对象的“假对象”,你可以随便指定它的方法返回什么、被调用了几次,目的就是隔离依赖——比如你测deletePerson时,不需要真的去数据库查人,用Mock的findOrThrow返回你预设的对象就行,这样测试不会受数据库状态影响。
3. 控制内部调用
当被测方法内部调用了其他方法(比如你这里的父类findOrThrow),如果这些调用依赖外部资源或者会干扰测试,你就需要控制它们的行为:要么让它返回预设值,要么跳过真实逻辑,这样才能专注测deletePerson本身的逻辑。
针对你的场景的具体解决方案
你的问题卡在父类的protected方法无法直接Mock/控制,这里给你两种简单可行的办法,都是新手能快速上手的:
方案1:写个测试专用子类(零额外依赖,最推荐)
因为protected方法在子类里是可以访问和重写的,所以我们可以创建一个MyServiceImpl的测试子类,重写findOrThrow方法,让它返回我们想要的对象:
// 在你的测试类里定义这个内部子类 class TestableMyServiceImpl extends MyServiceImpl { private Person mockPerson; public void setMockPerson(Person mockPerson) { this.mockPerson = mockPerson; } // 重写父类的protected方法,直接返回预设值 @Override protected Person findOrThrow(Long personId) { return mockPerson; } }
然后在测试方法里用这个子类:
@Test void testDeletePerson() { // 1. 创建测试子类实例 TestableMyServiceImpl service = new TestableMyServiceImpl(); // 2. 预设findOrThrow要返回的Person对象 Person testPerson = new Person(1L, "张三"); service.setMockPerson(testPerson); // 3. 调用要测的deletePerson方法 service.deletePerson(1L); // 4. 验证逻辑(比如如果deletePerson会调用DAO的delete方法,你可以Mock这个DAO并验证它被调用了) // 示例:Mockito.verify(dao).delete(1L); }
这个方法完全不用额外框架,逻辑直白,新手一眼就能懂,完美绕开了protected方法的访问限制。
方案2:Mockito Spy + 反射(适合不想重写方法的场景)
如果你不想重写整个findOrThrow,只是想临时控制它的返回值,可以用Mockito的Spy配合反射绕过protected的访问限制:
@Test void testDeletePersonWithSpy() throws Exception { // 1. 创建Spy对象:Spy会保留原对象的大部分逻辑,只修改你指定的方法 MyServiceImpl service = Mockito.spy(new MyServiceImpl()); // 2. 用反射获取父类的findOrThrow方法,取消访问限制 Method findOrThrowMethod = MyServiceBase.class.getDeclaredMethod("findOrThrow", Long.class); findOrThrowMethod.setAccessible(true); // 3. 指定调用findOrThrow(1L)时返回预设的Person Person testPerson = new Person(1L, "张三"); // 注意要用doReturn,不能用when(...).thenReturn(...),后者会触发真实方法调用 Mockito.doReturn(testPerson).when(service).findOrThrow(1L); // 4. 调用被测方法 service.deletePerson(1L); // 5. 验证逻辑 // 示例:Mockito.verify(dao).delete(1L); }
为啥你之前的尝试失败?
- 普通Mockito Spy不能直接拦截父类的protected方法,编译器会因为访问权限报错,因为测试类既不在父类的包下,也不是它的子类,没法直接引用这个方法。
- 用
when(...).thenReturn(...)的话,会先调用真实的findOrThrow方法,这可能会触发你不想看到的逻辑(比如查数据库),甚至导致错误,而doReturn不会触发真实调用。
内容的提问来源于stack exchange,提问作者aCarella
相关产品推荐
相关产品推荐

