java单元测试教程

还是记录一下单元测试常见的例子吧,要不每次都去查太麻烦了

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.18.0</version>
<scope>test</scope>
</dependency>
<!--静态方法用这个-->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>

常见的几种情况

其他服务

当你a类中的方法调用b服务的方法的时候,需要去mockb服务方法返回的时候直接使用when then即可

  1. 将b类mock一下
  2. when(b.method).then(returnValue);

静态方法

如果是静态方法的mock,需要使用mockito-inline

1
2
Mockito.mockStatic(StaticDemo.class);
when(StaticDemo.staticMethod()).thenReturn("mocked static method");

链式调用

1
2
3
4
5
6
7
8
//示例方法
Stirng name=StaticDemo.getUser.getUuserName();

//单元测试
Mockito.mockStatic(StaticDemo.class);
StaticDemo staticDemo=mock(StaticDemo.class);
when(StaticDemo.staticMethod()).thenReturn(staticDemo);
when(staticDemo.getUuserName()).thenReturn("userName");

多次调用

在同一个类中多次mock调用同一个类,调用同一个静态方法的情况,需要每次mock的时候,都去释放掉这个资源

mockStatic这个方法一定要给变量名字,否则无法自动释放,当然也会直接报错,如果有多个调用直接分号隔开,加在后面即可

1
2
3
4
5
try (MockedStatic<StaticDemo> mockedStatic = Mockito.mockStatic(StaticDemo.class);
MockedStatic<StaticDemo1> mockedStatic = Mockito.mockStatic(StaticDemo1.class)) {
when(StaticDemo.staticMethod()).thenReturn("mocked static method");
when(StaticDemo1.staticMethod()).thenReturn("mocked static method 1");
}

@value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.*;

class MyServiceTest {

@Test
void testGetGreeting_WithReflection() throws Exception {
// 1. 创建被测试对象
MyService myService = new MyService();

// 2. 使用反射,强行设置私有字段 appName 的值
Field field = MyService.class.getDeclaredField("appName");
field.setAccessible(true); // 允许访问私有字段
field.set(myService, "MyApp"); // 手动赋值

// 3. 调用方法并断言
String greeting = myService.getGreeting();
assertEquals("Hello, MyApp", greeting);
}
}

final

使用 mock(MyClass.class)然后对 final 方法 stub

✅ 必须引入 mockito-inline

异常

1
2
3
4
5
6
7
//junit4
@Test(expected = 异常类.class)
//junit5
assertThrows(
IllegalArgumentException.class,
() -> calculator.divide(10, 0)
);

调用同一个类中的一个方法

推荐使用spy来mock部分方法,被测方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CalculatorTest {
@spy//如果你要调用被测试类的一个方法,可以直接这么使用
@injectMock
private Calculator calculator;

@Test
public void testAddThenDoubleWithSpy() {
// 创建真实对象,然后用 Mockito.spy 包装它(部分 mock)
Calculator calculator = spy(new Calculator());

// 假设我们想 mock 内部调用的 add 方法,让它返回固定值 100,而不是真实计算
//当你想实际去调用的时候,不用spy的时候并且不用when就可以了
when(calculator.add(anyInt(), anyInt())).thenReturn(100);

int result = calculator.addThenDouble(2, 3); // 原本应该算 2+3=5,然后 5 * 2=10
// 但由于我们 mock 了 add 方法,让它返回 100,所以结果是 100 * 2 = 200
assertEquals(200, result);
}
}

void

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testNotify_WithValidMessage_ShouldCallVoidMethodAndReturnOk() {
// 准备
String message = "Hello, Mockito!";

// 调用 Controller 方法
var response = myController.notify(message);

// 验证返回值
assertEquals(200, response.getStatusCodeValue());
assertEquals("通知已发送", response.getBody());

// ✅ 验证:void 方法是否被调用了一次,且参数正确
verify(notificationService, times(1)).sendNotification(message);
}

常见问题

  1. mock出来的对象不正常,是个null

    这说明你mock的方式不对,mock这个方法的时候,是需要参数类型严格对账,比如入参是一个String类型的,你不能直接any(),你要anyString(),
    比如类型是JsonObject类型的,你不能直接any(),你要any(JsonObject.class)

  2. static类型的方法mockito是可以mock的,只是需要mockito-inline
  3. mockito的doReturn()和when()的区别,一般来说大部分场景使用when足够了,但是某些场景,比如一个类里面的方法a调用方法b,就需要用到doReturn了
    • when(...).thenReturn(...):适合“外部依赖” + “mock 对象”
    • doReturn(...).when(...):适合“内部方法” + “spy 对象” + “避免副作用”
  4. 想要覆盖抛出异常的类的话,还是要使用dotthrow或者when thenThrow 甚至是直接使用异常 在测试类中try catch
  5. @RunWith(MockitoJUnitRunner.class) 和MockitoAnnotations.initMocks(this)一个意思,不需要重复调用
  6. any你甚至可以any(Class.class)