为什么Mockito @Mock创建一个非模拟实例?

时间:2020-03-06 00:24:09

标签: java unit-testing mockito

我有一些非常相似的项目,全部是Java,SpringBoot和Maven。它们都具有相同的名称和几乎相同的内容。我在其中一个中添加了一个额外的方法,即要描述问题的类,但是我很确定细节是巧合。

每个项目还具有一个与该非常相似的类相对应的测试类,并且该测试类的框架在每个类中均相同。测试类具有用于受测类(CUT)的@InjectMocks和两个@Mock注释,其中一个对应于CUT的实例变量。

测试类确实具有@Before方法,该方法创建测试使用的实例变量。

测试类的所有变体均带有“ @RunWith(MockitoJUnitRunner.class)”。

如果我运行“好”测试中的一个并在@Before方法的第一行上设置一个断点,然后在变量窗格中查看“ this”变量,那么我会看到两个@ Mock-的类型。 ed实例变量以“ $ MockitoMock”结尾。

如果我在“错误”测试中做同样的事情,则两个@ Mock-ed变量的类型不以“ $MockitoMock”结尾。实际上,这些似乎是对应类的普通实例,而不是模拟类。

更奇怪的是,在“错误”测试中,我尝试在instvar = mock(clazz.class)方法中对“ @Before”进行显式调用,然后遍历这些变量,实例变量的类型为但是,当我单击实例变量时,toString面板仍显示“ Mock for ..., hashCode: 1028811481”,这仍然不是模拟类型。如果我此时“恢复”,则可以在其嘲笑的类中遇到断点,该实例的toString值显示为“ Mock for ...”。

这就是语言上的问题。现在,我想我将显示一些代码。

这是“不良”测试类别的一部分:

@RunWith(MockitoJUnitRunner.class)
public class RestClientTest {
    @InjectMocks
    RestClient restClient;

    @Mock
    RestClientFactory restClientFactory;

    @Mock
    RestTemplate restTemplate;

    HttpEntity<String> requestEntity;

    @Before
    public void setup() {
        requestEntity = new HttpEntity<>(new HttpHeaders());
        restClientFactory   = mock(RestClientFactory.class);
        restTemplate        = mock(RestTemplate.class);
        ReflectionTestUtils.setField(restClient, "restClientFactory", restClientFactory);
    }

这是“好”测试类的一部分:

@RunWith(MockitoJUnitRunner.class)
public class RestClientTest {
    @InjectMocks
    RestClient restClient;

    @Mock
    RestClientFactory restClientFactory;

    @Mock
    RestTemplate restTemplate;

    HttpEntity<String> requestEntity;

    @Before
    public void setup() {
        requestEntity = new HttpEntity<>(new HttpHeaders());
    }

我确定“好”和“坏”的项目都使用2.15.0版的Mockito-core。

更新

我尝试在不良测试中进入“模拟”调用,并在那里设置断点,因为它来自注释处理,因此我可以看到不良情况和良好情况的行为。

这是我在好情况下看到的:

first breakpoint of good case

我走到第65行,然后进入“ createMock()”。那使我进入了MockUtil类:

in MockUtil.createMock

“ mockMaker”的类型为“ org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker”。

我走到第35行并进入了“ mockMaker.createMock()”方法:

In BBMM.createMock()

现在让我们重新开始并运行“不良”案例:

我们首先达到了初始断点: first breakpoint in bad case

然后在这里: second breakpoint in bad case

现在,我们可以看到“ mockMaker”的类型与好的情况不同。类型为“ org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker”。

我将不继续执行此操作,但是此路径的确会生成具有不同toString值的“假模拟”。

现在我考虑一下,该实例就像一个“间谍”,因为它由Mockito管理,但是默认情况下所有方法都调用原始类方法。我不知道为什么这里要走另外一条路。

我希望这是足够的信息,可以为更好地了解其工作原理的人提供线索。

1 个答案:

答案 0 :(得分:2)

“ mockMaker”的类型(在“好的”情况下)是“ org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker”。

现在[在“坏”情况下],我们看到“ mockMaker”的类型与好情况不同。类型为“ org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker”。

因此,“好”项目使用的是默认的模拟制作器,后者使用子类化(请参见ByteBuddyMockMaker.java),而“坏”项目使用的是非默认模拟制作器,其尝试使用{{ 3}},以避免子类化:Java instrumentation。符合您观察到的行为差异

根据Javadoc的InlineByteBuddyMockMaker:

此模拟程序制造者必须明确激活,以支持模拟最终类型和方法:

可以通过创建包含文本Enumeration<URL> urls = ClassLoader.getSystemClassLoader().getResources("META-INF/bar.xml"); /mockito-extensions/org.mockito.plugins.MockMaker的文件mock-maker-inline来激活该模拟制作器。

因此,要弄清楚为什么会发生这种情况,您应该在类路径中进行搜索,以找出org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker资源是如何结束的。