Why is Mockito @Mock creating a non-mock instance?

David M. Karr :

I have a handful of very similar projects, all Java, SpringBoot, and Maven. All of them have one class with the same name, and almost identical contents. I added an additional method in one of them, the class with the problem I'm going to describe, but I'm pretty sure that detail is a coincidence.

Each project also has a test class corresponding to that very similar class, and the skeleton of that test class is identical in each class. The test class has an @InjectMocks for the class under test (CUT), and two @Mock annotations, one of which corresponds to an instance variable of the CUT.

The test class does have a @Before method that creates an instance variable used by the tests.

All the variations of the test class have "@RunWith(MockitoJUnitRunner.class)".

If I run one of the "good" tests and set a breakpoint on the first line of the @Before method and then look at the "this" variables in the variables pane, I see the types of the two @Mock-ed instance variables end with "$MockitoMock".

If I do the same thing in the "bad" test, the types of the two @Mock-ed variables do NOT end with "$MockitoMock". In fact, these appear to be normal instances of the corresponding classes, not mocked classes.

Even more curious, in the "bad" test, I tried making explicit calls to "instvar = mock(clazz.class)" in the @Before method, and after I step over those, the type of the instance variable is STILL not a mocked type, HOWEVER, when I click on the instance variable, the toString panel shows "Mock for ..., hashCode: 1028811481". If I "Resume" at this point, I can hit a breakpoint in the allegedly mocked class, with that same instance whose toString value says "Mock for ...".

That's the issue in words. Now I guess I'll show some code.

Here's part of the "bad" test class:

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

Here's part of the "good" test class:

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

I've determined that both "good" and "bad" projects are using version 2.15.0 of mockito-core.

Update:

I tried stepping into the "mock" call in the bad test, and set breakpoints there, because it goes here from annotation processing, so I can see the behavior of both the bad and good case.

Here's what I saw in the good case:

first breakpoint of good case

I stepped over until line 65 and stepped into "createMock()". That put me in the MockUtil class:

in MockUtil.createMock

The type of "mockMaker" is "org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker".

I stepped to line 35 and stepped into the "mockMaker.createMock()" method:

In BBMM.createMock()

Now let's start over and run the "bad" case:

We first hit the initial breakpoint: first breakpoint in bad case

And then here: second breakpoint in bad case

Now we see that the type of "mockMaker" is DIFFERENT from the good case. The type is "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker".

I'm not going to continue to step through this, but this path does produce the "fake mock" with the different toString value.

Now that I think about it, this instance is like a "spy" in that it's managed by Mockito, but all the methods call the original class method by default. I have no clue why it takes a different path here.

I would hope that this is enough information to give a clue to someone who better understands how this works.

ruakh :

The type of "mockMaker" [in the "good" case] is "org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker".

Now [in the "bad" case] we see that the type of "mockMaker" is DIFFERENT from the good case. The type is "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker".

So, the "good" project is using the default mock-maker, which uses subclassing — see ByteBuddyMockMaker.java — while the "bad" project is using a non-default mock-maker that tries to use Java instrumentation to avoid subclassing: InlineByteBuddyMockMaker.java. That matches up with the behavior difference that you observed

According to the Javadoc for InlineByteBuddyMockMaker:

This mock maker must to be activated explicitly for supporting mocking final types and methods:

This mock maker can be activated by creating the file /mockito-extensions/org.mockito.plugins.MockMaker containing the text mock-maker-inline or org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.

So to figure out why this is happening, you should search in your classpath to find out how the /mockito-extensions/org.mockito.plugins.MockMaker resource is ending up in there.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=27083&siteId=1