Mockito: 向私有 @Autowired 字段注入真实对象

Mockito: 向私有 @Autowired 字段注入真实对象

技术背景

在使用 Mockito 进行单元测试时,我们常使用 @Mock@InjectMocks 注解来注入依赖到被 @Autowired 注解标记的私有字段。然而,有时我们可能需要向这些私有 @Autowired 字段注入真实对象,而非仅仅是模拟对象。例如处理遗留代码时,为了避免大量的 when(...).thenReturn(...) 语句来设置模拟对象,使用真实对象会更加方便。

实现步骤

方法一:使用 @Spy 注解

  1. 创建一个使用 @Spy 注解的字段,并初始化该字段为真实对象的实例。
  2. 使用 @InjectMocks 注解标记需要注入依赖的对象。

示例代码如下:

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
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoJUnitRunner;
import org.mockito.Spy;

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
@Spy
private SomeService service = new RealServiceImpl();

@InjectMocks
private Demo demo;

// ...
}

class Demo {
@Autowired
private SomeService service;

// ...
}

interface SomeService {}

class RealServiceImpl implements SomeService {}

方法二:使用 Spring 的 ApplicationContext

  1. 注入 ApplicationContext
  2. @Before 方法中,从 ApplicationContext 获取真实对象的实例,并赋值给 @Spy 注解标记的字段。

示例代码如下:

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
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoJUnitRunner;
import org.mockito.Spy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
@Autowired
private ApplicationContext ctx;

@Spy
private SomeService service;

@InjectMocks
private Demo demo;

@Before
public void setUp() {
service = ctx.getBean(SomeService.class);
}

// ...
}

class Demo {
@Autowired
private SomeService service;

// ...
}

interface SomeService {}

方法三:使用 ReflectionTestUtils

  1. 使用 ReflectionTestUtils.setField 方法将真实对象注入到私有字段。

示例代码如下:

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
import org.junit.jupiter.api.BeforeEach;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.springframework.test.util.ReflectionTestUtils;

class Foo {
private Bar bar;

// ...
}

class BarImpl implements Bar {}

interface Bar {}

public class FooTest {
@Spy
// ...
@InjectMocks
Foo foo;

@BeforeEach
void _before() {
ReflectionTestUtils.setField(foo, "bar", new BarImpl());
}
}

核心代码

使用 @Spy 注解的核心代码

1
2
3
4
@Spy
private SomeService service = new RealServiceImpl();
@InjectMocks
private Demo demo;

使用 ApplicationContext 的核心代码

1
2
3
4
5
6
7
8
@Autowired
private ApplicationContext ctx;
@Spy
private SomeService service;
@Before
public void setUp() {
service = ctx.getBean(SomeService.class);
}

使用 ReflectionTestUtils 的核心代码

1
ReflectionTestUtils.setField(foo, "bar", new BarImpl());

最佳实践

  • 尽量使用构造函数注入:Mockito 不是一个依赖注入框架,并且依赖注入框架鼓励使用构造函数注入而不是字段注入。在设计类时,尽量使用构造函数来设置依赖,这样可以使代码更加清晰和可测试。
  • 谨慎使用 @Spy 注解:虽然 @Spy 注解可以用于注入真实对象,但它也可能导致测试类变得脆弱、不直观和容易出错。在使用 @Spy 注解之前,请仔细阅读 spy() 的 Javadoc。

常见问题

  • Mockito 无法模拟/监视某些类型:Mockito 无法模拟/监视 final 类、匿名类和基本类型。例如,如果你尝试对 String 类型使用 @Spy 注解,Mockito 会抛出异常。
  • 使用 @Spy 注解时注入失败:在某些情况下,Mockito 可能无法将 @Spy 注解标记的对象注入到 @InjectMocks 注解标记的对象中。此时可以尝试使用 ReflectionTestUtils 或其他方法。

Mockito: 向私有 @Autowired 字段注入真实对象
https://119291.xyz/posts/2025-04-30.mockito-inject-real-objects-into-private-autowired-fields/
作者
ww
发布于
2025年4月30日
许可协议