Mockito: Inject real objects into private @Autowired fields

Mockito: Inject real objects into private @Autowired fields

技术背景

在使用 Mockito 进行单元测试时,通常会使用 @Mock@InjectMocks 注解来注入依赖到使用 Spring 的 @Autowired 注解标注的私有字段。但有时,我们可能需要将真实对象注入到这些私有 @Autowired 字段中,而不只是注入模拟对象。这在处理遗留代码时尤为有用,因为设置模拟对象可能需要大量的 when(...).thenReturn(...) 语句来避免空指针异常等问题。

实现步骤

方法一:使用 @Spy 注解

  1. 创建一个测试类,并使用 @RunWith(MockitoJUnitRunner.class) 注解。
  2. 使用 @Spy 注解标注一个真实对象的实例。
  3. 使用 @InjectMocks 注解标注要注入依赖的类的实例。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
@Spy
private SomeService service = new RealServiceImpl();

@InjectMocks
private Demo demo;

/* ... */
}

public class Demo {
@Autowired
private SomeService service;
/* ... */
}

在上述代码中,RealServiceImpl 实例会被注入到 demo 对象中。

方法二:使用 Spring 的 ApplicationContext

  1. 创建一个测试类,并使用 @RunWith(MockitoJUnitRunner.class) 注解。
  2. 使用 @Inject 注解注入 ApplicationContext
  3. 使用 @Spy 注解标注要注入的服务。
  4. @Before 方法中,从 ApplicationContext 获取真实对象实例并赋值给 @Spy 注解标注的字段。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
@Inject
private ApplicationContext ctx;

@Spy
private SomeService service;

@InjectMocks
private Demo demo;

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

/* ... */
}

方法三:使用 ReflectionTestUtils

  1. 在测试类中使用 @Spy@Mock@InjectMock 注解。
  2. @BeforeEach 方法中,使用 ReflectionTestUtils.setField 方法将真实对象注入到私有字段中。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Spy
..
@Mock
..

@InjectMock
Foo foo;

@BeforeEach
void _before(){
ReflectionTestUtils.setField(foo,"bar", new BarImpl());// `bar` is private field
}

方法四:使用构造函数注入

  1. 在测试类中声明一个模拟对象和要测试的类的实例。
  2. @BeforeEach 方法中,通过构造函数将模拟对象或真实对象传递给要测试的类的实例。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
@Mock
private SomeService serviceMock;

private Demo demo;

/* ... */
@BeforeEach
public void beforeEach(){
demo = new Demo(serviceMock);
}

方法五:使用 JUnit5/Mockito 扩展

如果需要注入 String 等对象,可以使用 JUnit5/Mockito 扩展,例如 mockito-object-injection

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
@InjectionMap
private Map<String, Object> injectionMap = new HashMap<>();

@BeforeEach
public void beforeEach() throws Exception {
injectionMap.put("securityEnabled", Boolean.TRUE);
}

@AfterEach
public void afterEach() throws Exception {
injectionMap.clear();
}

最佳实践

  • 使用构造函数注入:在设计类时,尽量使用构造函数注入依赖,这样可以使代码更易于测试,避免使用反射等复杂的注入方式。
  • 谨慎使用 @Spy@Spy 注解会创建一个真实对象的部分模拟,可能会使测试变得复杂和脆弱。只有在处理遗留代码或无法轻易更改的代码时才使用。
  • 遵循单一职责原则:确保每个类的职责单一,这样可以减少依赖注入的复杂性,使测试更加简单和可靠。

常见问题

  • Mockito 无法模拟 final 类、匿名类和基本类型:如果需要注入 String 等 final 类,可以使用其他方法,如 JUnit5/Mockito 扩展。
  • NullPointerException:在使用 @Before@BeforeEach 方法时,确保对象已经正确初始化。如果使用 Spring 的 ApplicationContext,确保 Spring 上下文已经正确加载。
  • 部分模拟的问题:使用 @Spy 注解可能会导致部分模拟的问题,使测试变得复杂。在使用 @Spy 之前,仔细阅读 spy() 的 Javadoc,了解其潜在的问题。

Mockito: Inject real objects into private @Autowired fields
https://119291.xyz/posts/2025-04-29.mockito-inject-real-objects-into-private-autowired-fields/
作者
ww
发布于
2025年4月29日
许可协议