Mockito: 向私有 @Autowired 字段注入真实对象
技术背景
在使用 Mockito 进行单元测试时,我们常使用 @Mock
和 @InjectMocks
注解来注入依赖到被 @Autowired
注解标记的私有字段。然而,有时我们可能需要向这些私有 @Autowired
字段注入真实对象,而非仅仅是模拟对象。例如处理遗留代码时,为了避免大量的 when(...).thenReturn(...)
语句来设置模拟对象,使用真实对象会更加方便。
实现步骤
方法一:使用 @Spy 注解
- 创建一个使用
@Spy
注解的字段,并初始化该字段为真实对象的实例。 - 使用
@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
- 注入
ApplicationContext
。 - 在
@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
- 使用
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
或其他方法。