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


190

我正在使用Mockito @Mock@InjectMocks批注将依赖项注入到使用Spring批注的私有字段中@Autowired

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Mock
    private SomeService service;

    @InjectMocks
    private Demo demo;

    /* ... */
}

public class Demo {

    @Autowired
    private SomeService service;

    /* ... */
}

现在,我也想将真实对象注入私有@Autowired字段(不使用setter)。这是否可能,或者该机制仅限于注入Mocks?


5
通常,当您在嘲笑事物时,它意味着您不太在乎具体的对象。您只真正关心模拟对象的行为。也许您想进行集成测试?或者,您能否提供一个理由,说明为什么要将模拟对象和具体对象放在一起生活?
Makoto

2
好吧,我正在处理遗留代码,这将需要大量的when(...)。thenReturn(...)语句来设置模拟,只是为了防止某些NPE之类。另一方面,可以安全地使用真实对象。因此,选择将真实对象与模拟一起注入将非常方便。即使这可能是代码异味,在这种特殊情况下,我也认为这是合理的。
user2286693

不要忘了MockitoAnnotations.initMocks(this);@Before方法。我知道这与原始问题没有直接关系,但是与以后出现的任何人都需要添加此元素以使其可运行。
Cuga 2015年

7
@Cuga:如果您将Mockito运行器用于JUnit(@RunWith(MockitoJUnitRunner.class)),则不需要该行MockitoAnnotations.initMocks(this);
Clint Eastwood

1
谢谢-我从不知道,而且一直都指定两者
-Cuga

Answers:


304

使用@Spy注释

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

    @InjectMocks
    private Demo demo;

    /* ... */
}

Mockito会将具有@Mock@Spy批注的所有字段视为要注入到带有@InjectMocks批注的实例中的潜在候选对象。在上述情况下,'RealServiceImpl'实例将被注入“演示”

有关更多详细信息,请参阅

样板房

@间谍

@嘲笑


9
+1:为我工作...除了String对象。Mockito抱怨​​:Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
Adrian Pronk

谢谢它也为我工作:)为了使代理使用其他模拟进行真正的实现,请使用间谍
Swarit Agarwal

谢谢!那正是我所需要的!
nterry

2
就我而言,Mockito 没有注入间谍。它确实注入了一个模拟。该字段是私有的,没有二传手。
Vituel '16

8
顺便说一句,有没有必要new RealServiceImpl()@Spy private SomeService service;使用默认的构造函数反正刺探它之前创建一个真正的对象。
parxier

20

除了@Dev Blanked答案之外,如果您想使用由Spring创建的现有bean,可以将代码修改为:

@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);
    }

    /* ... */
}

这样,您无需更改代码(添加其他构造函数)即可使测试正常工作。


2
@Aada你能详细说明吗?
Yoaz Menda

1
它来自哪个库,我只能在org.mockito中看到InjectMocks
Sameer

1
@sameer import org.mockito.InjectMocks;
Yoaz Menda

添加评论以取消Aada的评论。这对我有用。正如其他答案所建议的那样,我无法“根本不使用字段注入”,因此我必须确保Autowired依赖项中的字段已正确创建。
Scrambo

1
我已经试过了。但是我从设置方法中收到空指针异常
Akhil Surapuram

3

Mockito不是DI框架,甚至DI框架都鼓励构造函数注入而不是字段注入。
因此,您只需声明一个构造函数即可设置要测试的类的依赖项:

@Mock
private SomeService serviceMock;

private Demo demo;

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

spy在一般情况下使用Mockito 是一个糟糕的建议。它使测试类变脆,不简单并且容易出错:真正被嘲笑的是什么?真正测试了什么?
@InjectMocks而且它@Spy还会鼓励整个课程的设计,因为它会鼓励classes肿的课堂和课堂中的混合职责。
请在盲目使用Javadoc之前先阅读spy()它(强调不是我的):

创建真实对象的间谍。除非调用它们,否则间谍将调用真实方法。真正的间谍应该小心谨慎地使用,例如在处理遗留代码时。

与往常一样,您将阅读partial mock warning:面向对象的编程通过将复杂度划分为单独的特定SRPy对象来解决复杂度。部分模拟如何适应这种范例?好吧,事实并非如此……部分模拟通常意味着复杂性已移至同一对象的不同方法。在大多数情况下,这不是您设计应用程序的方式。

但是,在少数情况下,局部模拟会派上用场:处理您无法轻松更改的代码(第三方接口,遗留代码的临时重构等)。但是,我不会将局部模拟用于新的,测试驱动的,设计的代码。


0

在Spring中,有一个专用的实用程序ReflectionTestUtils为此目的而调用。以特定实例为例,并注入到该字段中。


@Spy
..
@Mock
..

@InjectMock
Foo foo;

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

-1

我知道这是一个古老的问题,但是在尝试注入字符串时我们遇到了同样的问题。因此,我们发明了一个JUnit5 / Mockito扩展,它完全可以满足您的需求:https : //github.com/exabrial/mockito-object-injection

编辑:

@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();
 }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.