How to mock Logger when created with the @Slf4j annotation?
Asked Answered
F

4

20

I have a class that has the @Slf4j annotation.

I try to write a test and mock the Logger, but it does not work.

@RequiredArgsConstructor
@Slf4j
public abstract class ExampleClass {
    
    protected final PropsClass properties;
    
    protected void logInfo(..) {
        log.info(...);
        clearMappedDiagnosticContext();
    }
}

This is how the test looks like:

@RunWith(MockitoJUnitRunner.class)
public class ExampleClassTest {
    
    @Mock
    Logger logger;
    
    @Mock
    PropsClass properties;
    
    @InjectMocks
    ExampleClass exampleClass;
    
    @Test
    public void logSomethingtest() {
        ...
        exampleClass.logInfo(...);
        Mockito.verify(logger).info(marker, "foo bar {}", ...);
    }

This is the error I get:

Wanted but not invoked:
logger.info(
    MY_MARKER,
    "..........",
    "....",
    "....",
    0L
);

Actually, there were zero interactions with this mock.

The question is, how to mock the Logger?

Fur answered 26/3, 2021 at 11:58 Comment(1)
If you really need to test, it use slf4j-test.Commonable
B
13

The lombok @Slf4j annotation injects code into your class at compile time. Specifically, it will add the following code to your class:

private static final org.slf4j.Logger log =
                        org.slf4j.LoggerFactory.getLogger(LogExample.class);

@InjectMocks is telling Mockito to create an instance of your class and inject mocks as its dependencies at runtime.

The logger is injected at compile time. Dependecies are injected at runtime. That's why your logger is not mocked and cannot be mocked like this. If you look at the injected logger code above, you will understand, that the only way to mock the logger is to mock the LoggerFactory (Mockito can mock static methods since version 3.4, IIRC) and make it return the logger mock.

NB: Making mocks return mocks is usually a bad idea and should be avoided. @Slf4j is too convenient to not be used. It's a tradeoff.

NB: If all you want it silencing the logger, then you could also just configure it to shut up in your tests.

Bemis answered 27/3, 2021 at 7:19 Comment(7)
When I tried to mock LoggerFactory I had this error: org.mockito.exceptions.base.MockitoException: Cannot mock/spy class org.slf4j.LoggerFactory Mockito cannot mock/spy because : - final class at com.marqeta.mfm.job.JobSourceItemServiceTest.<init>(JobSourceItemServiceTest.java:60) at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:250) Can you add a code example of how LoggerFactory could be mocked?Froh
@BrunoNegrãoZica: Let me ask you a question first: Why do you want to mock the logger?Bemis
I made the question firstFroh
Could you please explain how to "configure it to shut up in your tests"?Fortieth
@geneb. That depends on your setup. General approach: Set the log level for the test environment to OFF.Bemis
To nitpick @EricSchaefer's answer, it's not a matter of compile-time versus runtime, it's a matter of ordering at runtime. The Logger field generated due to the @Slf4j annotation is static and will be populated when the class is loaded at runtime, but the mock will be created after that.Currie
Sure, but the code is injected at compile-time and it hard-wires the logger, hence no replacement of the logger at run-time via dependency injection.Bemis
O
5

As Mockito is able to mock also static methods since version 3.4.0 is able to mock static methods too, you can simply stub the access to to logger like this:

@Test
void logmessageInCaseOfPositiveValidationIsWritten() {
    Logger mockedLogger = Mockito.mock(Logger.class);

    try (MockedStatic<LoggerFactory> context = Mockito.mockStatic(LoggerFactory.class)) {
        context.when(() -> LoggerFactory.getLogger(Mockito.any(Class.class)))
            .thenReturn(mockedLogger);

            // Your test code and your assertions

            Mockito.verify(mockedLogger)
                .debug("I am the expected message with param {}",
                "paramvalue");
    }
}

Do not forget to create the needed file test/resources/mockito-extensions/org.mockito.plugins.MockMaker with the context below to enable static mocking:

mock-maker-inline

Written for version 4.5.1 of Mockito.

Oas answered 15/9, 2023 at 6:57 Comment(0)
S
2

You can use https://www.simplify4u.org/slf4j-mock/ library.

Your example code will work - you only need slf4j-mock on your dependency instead of other slf4j binding.

Sergio answered 30/7, 2021 at 21:57 Comment(0)
M
0

Problem with excluding slf4j is that, You will not have any provider for it in application context

Majunga answered 22/5 at 19:14 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Devilkin

© 2022 - 2024 — McMap. All rights reserved.