javaspring-bootmockitospring-boot-test

Mockito it's instantiating with null the builder in the mocked class


After migrate from Java 11 to 17 and Springboot 2 to 3, a test start to fail with NPE. After some investigation I found this mock instance give me a NPE in the builder:

Tracer interface is -> import io.micrometer.tracing.Tracer;

Tracer its an interface from micrometer package.

@Mock private Tracer tracer;

tracer.traceContextBuilder().traceId(XXX).build();

In this case traceContextBuilder is null.

I'm using mockito-core:5.7.0. An this are my test dependencies:

testImplementation 'org.junit.platform:junit-platform-launcher:1.5.2'
testImplementation 'org.junit.platform:junit-platform-runner:1.5.2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation group: 'junit', name: 'junit', version: '4.+'

Previously I can use this without a NPM. It's there any way to generate a mock with this traceContextBuilder not in null state?

I'm preety new at Mockito. Is there any way to initialize a Mock with not null parameters (like a new object or something). Or maybe I need to instanciate the object and use an @Spy annotation? A more general question it's. Can I instantiate Mock with all the fields availables? Like a real object?

Thanks in advance.


Solution

  • IMHO the best option here is to not use Mockito here at all. Micrometer's Tracer interface already defines a dummy noop Tracer that can be used in tests:

    private Trace tracer = Tracer.NOOP;
    

    No mock setup and everything just works™ in your tests: the tracer does nothing, which is usually want you want from the tracer in your test.


    If you really must to do this with Mockito, continue reading.

    Mockito mocks by default return a default value for any given return type. That is false for booleans and Booleans, 0 for numeric types (primitive or boxed), empty collections and empty maps for collection and map types, empty() for Optionals, null for Strings and other Objects. (I think there are a few more, such as AtomicReference, which returns an AtomicReference(null)).

    Since you have not stubbed your mock to return a non-default answer, it will simply return the default as described above; in your case null for complex objects.

    You have at least two options to solve this with Mockito:

    1. Stub all calls that you expect
    @Mock private Tracer tracer;
    @Mock private TraceContext.Builder traceContextBuilderMock;
    
    when(tracer.traceContextBuilder()).thenReturn(traceContextBuilderMock);
    when(traceContextBuilderMock.traceId(anyString())).thenReturn(traceContextBuilderMock);
    tracer.traceContextBuilder().traceId(XXX).build();
    
    1. Mock with Answers.RETURNS_DEEP_STUBS
    @Mock(answer = Answers.RETURN_DEEP_STUBS) private Tracer tracer;
    tracer.traceContextBuilder().traceId(XXX).build();