前言
使用Mockito和JUnit5进行单元测试。
需要注入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.12.4</version> <scope>test</scope> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency>
|
基础知识
- 使用@Test注解标记测试方法。
- 测试代码和被测试代码放到同一个包中。
示例背景
- 假设有一个服务类
MockService
,其中有一个serviceMethod
方法。1 2 3
| public class MockService { public String serviceMethod(String name) ; }
|
- 假设有一个临时类
MockTmp
,其中有一个tmpMethod
方法。MockTmp
的构造器需要传入一个MockService
对象。1 2 3 4 5 6 7 8
| public class MockTmp {
private final MockService mockService;
public MockTmp(MockService mockService);
public String tmpMethod(String name); }
|
- 假设有一个被测试类
MockUser
,其中包含了MockService
类、一个私有方法、一个静态方法、一个公有方法。1 2 3 4 5 6 7 8 9 10 11 12
| public class MockUser {
private final MockService mockService;
public MockUser(MockService mockService);
private String privateMethod(String name) ;
public static String staticMethod(String name);
public String publicMethod(String name); }
|
测试将会覆盖:私有方法测试、静态方法测试、公有方法测试、构造器测试。
基本用法
创建Mock对象
- 使用
@Mock
注解或Mockito.mock
方法创建Mock对象。
- 使用
@Spy
注解或Mockito.spy
方法创建Mock对象。
- 使用
MockBean
注解创建Mock对象。
- 使用
@InjectMocks
注解或MockitoAnnotations.initMocks
方法注入Mock对象。
1 2 3 4 5 6 7 8 9 10 11
| @Mock private MockService mockService;
@Spy @InjectMocks private MockUser mockUser;
@BeforeEach public void setUp(){ MockitoAnnotations.openMocks(this); }
|
注意:@Mock
和@Spy
注解的区别:
@Mock
注解创建的对象是一个真正的Mock对象,它会覆盖被Mock的方法。
@Spy
注解创建的对象是一个真实的对象,它会保留被Mock的方法的原始实现。
Mock方法
- 使用
Mockito.when
方法模拟方法调用。
- 使用
Mockito.doReturn
方法模拟方法调用。
- 使用
Mockito.doThrow
方法模拟方法调用。
- 使用
Mockito.doNothing
方法模拟方法调用。
- 使用
Mockito.doAnswer
方法模拟方法调用。
…
1 2 3 4 5 6 7 8
| Mockito.when(mockService.serviceMethod("Tom")).thenReturn("Hello, Tom!");
Mockito.doReturn("Hello, Tom!").when(mockService).serviceMethod("Tom");
Mockito.doThrow(new RuntimeException()).when(mockService).serviceMethod("Tom");
Mockito.doNothing().when(mockService).serviceMethod("Tom"); ...
|
verify方法和assert方法
- 使用
Mockito.verify
方法验证方法调用。
- 使用
Assert.assertEquals
方法断言方法调用。
- 使用
Assert.assertTrue
方法断言方法调用。
…1 2 3 4 5 6
| Mockito.verify(mockService, Mockito.times(1)).serviceMethod("Tom");
Assert.assertEquals("Hello, Tom!", mockService.serviceMethod("Tom"));
Assert.assertTrue("Hello, Tom!".equals(mockService.serviceMethod("Tom"))); ...
|
示例代码
测试私有方法
被测方法:
1 2 3 4 5
| private String privateMethod(String name) { System.out.println("Private Method Called"); name = name + " private"; return name; }
|
测试方法:
1 2 3 4 5 6 7 8 9
| @Test void privateMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { String name = "test"; Method method = mockUser.getClass().getDeclaredMethod("privateMethod", String.class); method.setAccessible(true); String result = (String) method.invoke(mockUser, name); assertEquals("test private", result); }
|
mockito无法直接模拟私有方法,需要通过反射调用。
测试静态方法
被测方法:
1 2 3 4 5
| public static String staticMethod(String name) { System.out.println("Static Method Called"); name = name + " static"; return name; }
|
测试方法:
1 2 3 4 5 6 7
| @Test void staticMethod() { String name = "test"; String result = MockUser.staticMethod(name); assertEquals("test static", result); }
|
测试公有方法
被调用的所有方法都mock掉,只测试公有方法。这样可以保证测试的是公有方法的逻辑,而不是依赖的其他方法。
被测方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public String publicMethod(String name) { try{ String name1 = mockService.serviceMethod(name); String name2 = privateMethod(name); String name3 = staticMethod(name); MockService mockService2 = new MockService(); String name4 = mockService2.serviceMethod(name); MockTmp mocktmp = new MockTmp(mockService2); String name5 = mocktmp.tmpMethod(name);
return name1 + " " + name2 + " " + name3 + " " + name4 + " " + name5; } catch (RuntimeException e){ throw new RuntimeException("Exception"); } }
|
测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Test void publicMethod() { String name = "test"; Mockito.when(mockService.serviceMethod(anyString())).thenReturn("test");
try(MockedStatic<MockUser> mockUserStatic = Mockito.mockStatic(MockUser.class); MockedConstruction<MockService> service = Mockito.mockConstruction(MockService.class, (mock, context) -> { Mockito.when(mock.serviceMethod(anyString())).thenReturn("test"); }); MockedConstruction<MockTmp> tmp = Mockito.mockConstruction(MockTmp.class, (mock, context) -> { Mockito.when(mock.tmpMethod(anyString())).thenReturn("test"); }) ){
mockUserStatic.when(() -> MockUser.staticMethod(anyString())).thenReturn("test");
String result = mockUser.publicMethod(name); assertEquals("test test private test test test", result); } }
|
最后输出的结果是test test private test test test
。
可以看出,只有privateMethod
方法没有被mock
掉,其他方法都被mock
掉了。
目前还没有找到方法去mock
类内私有方法(基于不能修改源代码、也不能使用powerMockito)。(如果有大佬看到,求教,邮箱1763605980@qq.com)
异常处理
1 2 3 4 5 6
| @Test public void publicMethodException() { String name = "test"; Mockito.doThrow(new RuntimeException()).when(mockService).serviceMethod(anyString()); assertThrows(RuntimeException.class, () -> mockUser.publicMethod(name)); }
|
完整代码
MockService
类:
1 2 3 4 5 6 7 8
| @Service public class MockService { public String serviceMethod(String name) { System.out.println("serviceMethod"); name = name + " service"; return name; } }
|
MockTmp
类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.example.demo.mockito;
public class MockTmp { private final MockService mockService;
public MockTmp(MockService mockService){ this.mockService = mockService; };
public String tmpMethod(String name) { return name + " tmp"; } }
|
MockUser
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.example.demo.mockito;
public class MockUser { private final MockService mockService;
public MockUser(MockService mockService){ this.mockService = mockService; };
private String privateMethod(String name) { System.out.println("Private Method Called"); name = name + " private"; return name; }
public static String staticMethod(String name) { System.out.println("Static Method Called"); name = name + " static"; return name; }
public String publicMethod(String name) { try{ String name1 = mockService.serviceMethod(name); String name2 = privateMethod(name); String name3 = staticMethod(name); MockService mockService2 = new MockService(); String name4 = mockService2.serviceMethod(name); MockTmp mocktmp = new MockTmp(mockService2); String name5 = mocktmp.tmpMethod(name);
return name1 + " " + name2 + " " + name3 + " " + name4 + " " + name5; } catch (RuntimeException e){ throw new RuntimeException("Exception"); } } }
|
MockUserTest
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| package com.example.demo.mockito;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.*;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString;
class MockUserTest {
@Mock private MockService mockService;
@Spy @InjectMocks private MockUser mockUser;
@BeforeEach public void setUp(){ MockitoAnnotations.openMocks(this); }
@Test void privateMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { String name = "test"; Method method = mockUser.getClass().getDeclaredMethod("privateMethod", String.class); method.setAccessible(true); String result = (String) method.invoke(mockUser, name); assertEquals("test private", result); }
@Test void staticMethod() { String name = "test"; String result = MockUser.staticMethod(name); assertEquals("test static", result); }
@Test void publicMethod() { String name = "test"; Mockito.when(mockService.serviceMethod(anyString())).thenReturn("test");
try(MockedStatic<MockUser> mockUserStatic = Mockito.mockStatic(MockUser.class); MockedConstruction<MockService> service = Mockito.mockConstruction(MockService.class, (mock, context) -> { Mockito.when(mock.serviceMethod(anyString())).thenReturn("test"); }); MockedConstruction<MockTmp> tmp = Mockito.mockConstruction(MockTmp.class, (mock, context) -> { Mockito.when(mock.tmpMethod(anyString())).thenReturn("test"); }) ){
mockUserStatic.when(() -> MockUser.staticMethod(anyString())).thenReturn("test");
String result = mockUser.publicMethod(name); assertEquals("test test private test test test", result); } }
@Test public void publicMethodException() { String name = "test"; Mockito.doThrow(new RuntimeException()).when(mockService).serviceMethod(anyString()); assertThrows(RuntimeException.class, () -> mockUser.publicMethod(name)); } }
|
期望抛出异常
1 2 3 4 5 6
| @Test(expected = RuntimeException.class) public void publicMethodException() { String name = "test"; Mockito.doThrow(new RuntimeException()).when(mockService).serviceMethod(anyString()); assertThrows(RuntimeException.class, () -> mockUser.publicMethod(name)); }
|
doThrow() 与 thenThrow() 区别(用于 stubbing 有返回值方法)
- doThrow():用于 void 方法的 stubbing。
- thenThrow():用于有返回值的方法的 stubbing。
1 2 3 4
| doThrow(new RuntimeException()).when(mockObject).voidMethod();
when(mockObject.method()).thenThrow(new RuntimeException());
|
如果 源代码中有 try-catch
块,而在测试方法上使用了:
1
| @Test(expected = Exceptions.class)
|
那你根本无法覆盖 catch
中的内容,原因是:
JUnit 的 @Test(expected = ...)
会中断执行流程
当测试中抛出指定异常时,JUnit 会立即判定测试通过,不再执行后续逻辑:
- 如果源代码中 没有 catch,这个方式可以验证是否真的抛出了异常。
- 但如果源代码中 已经 catch 掉异常,异常就不会冒泡到测试层,
@Test(expected = ...)
就无效。
- 即使异常冒泡成功了,**
catch
块内容也不会执行到**,所以覆盖率里 catch 是“未执行”。
✅ 要覆盖 catch 中的代码,正确做法是:
不使用 @Test(expected = ...)
,而是让测试代码走完整流程,触发并验证 catch 块被执行。
1 2 3 4 5 6 7 8 9 10
| @Test public void testRemoveUser_withException() { when(userService.deleteUserById(1L)).thenThrow(new Exceptions("boom"));
controller.removeUser(1L);
}
|