Fork me on GitHub

Introduction

Yet another way to testing logging in application which use SLF4J.

Features

  • this binding support for easy create Mockito mock for Logger
  • call to Logger can be delegated to instance of SimpleLogger, so we can create standard simplelogger.properties
  • support for testing and mocking MDC
  • light transitive dependencies - only slf4j-api and mockito-core
  • support testing in parallel in multi thread
  • all the Magic are done by Mockito plugins, so you don't need to directly use class from this library
  • ease use

Contributing and helps

Contributions are welcome!

  • Give a ⭐ - if you want to encourage me to work on a project
  • Don't hesitate create issue for a new feature you dream of or if you suspect some bug - new issue
  • If my work is valuable for you can consider to a sponsor for me

How to use

First we should add slf4j-mock binding or slf4j2-mock provider as dependency to our project.

For slf4j-api 1.7.?? and earlier.

<dependency>
    <groupId>org.simplify4u</groupId>
    <artifactId>slf4j-mock</artifactId>
    <version>2.3.0</version>
    <scope>test</scope>
</dependency>

For slf4j-api 2.0.?? and later.

<dependency>
    <groupId>org.simplify4u</groupId>
    <artifactId>slf4j2-mock</artifactId>
    <version>2.3.0</version>
    <scope>test</scope>
</dependency>

We must remember that we can have only one SLF4J binding or provider on classpath, so look for dependencies like slf4j-simple, slf4j-log4j12, slf4j-jdk14 or slf4j-nop and remove those from project when you want testing logging behavior.

Now we have class which does some logging action

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Example {

    private static final Logger LOGGER = LoggerFactory.getLogger(Example.class);

    public void methodWithLogInfo(String message) {
        LOGGER.info(message);
    }
}

JUnit4

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.slf4j.Logger;

@RunWith(MockitoJUnitRunner.class)
public class JUnit4ExampleTest {

    private static final String INFO_TEST_MESSAGE = "info log test message from JUnit4";

    @Mock
    Logger logger;

    @InjectMocks
    Example sut;

    @Test
    public void logInfoShouldBeLogged() {

        // when
        sut.methodWithLogInfo(INFO_TEST_MESSAGE);

        // then
        Mockito.verify(logger).info(INFO_TEST_MESSAGE);
        Mockito.verifyNoMoreInteractions(logger);
    }
}

JUnit5

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;

@ExtendWith(MockitoExtension.class)
class JUnit5ExampleTest {

    private static final String INFO_TEST_MESSAGE = "info log test message from JUnit5";

    @Mock
    Logger logger;

    @InjectMocks
    Example sut;

    @Test
    void logInfoShouldBeLogged() {

        // when
        sut.methodWithLogInfo(INFO_TEST_MESSAGE);

        // then
        Mockito.verify(logger).info(INFO_TEST_MESSAGE);
        Mockito.verifyNoMoreInteractions(logger);
    }
}

MDC support

Example for MDC testing

...
import org.slf4j.spi.MDCAdapter;

class MDCTest {

        @Mock
        MDCAdapter mdcMock;

        @Test
        void testPut() {

            MDC.put("key", "value");

            verify(mdcMock).put("key", "value");
            verifyNoMoreInteractions(mdcMock);
        }

        @Test
        void testGet() {

            when(mdcMock.get("key")).thenReturn("value");

            assertThat(MDC.get("key")).isEqualTo("value");

            verify(mdcMock).get("key");
            verifyNoMoreInteractions(mdcMock);
        }
}

As we see in this case we only need Mock for MDCAdapter

Mock and Spy

In tests, we can use Mockito annotation to define Logger

  • @Mock - create Mock for Logger - all call we be done on Mock, so any output will not occur from Logger

  • @Spy - create Spy with SimpleLogger - all call we be done on real methods, so standard behavior for SimpleLogger will be kept

Bind Mock to Logger

One Mock, one Logger in class under test

You simply define:

        @Mock
        Logger logger;

        @InjectMocks
        private Example sut;

In this case Example class must have defined one Logger, which be used.

Named Mock for Logger

When we want to use in one test more than one Logger we must create Mock with a name.

        @Mock(name = "org.simplify4u.slf4jmock.test.Example1")
        Logger logger1;

        @Mock(name = "org.simplify4u.slf4jmock.test.Example2")
        Logger logger1;

Named Spy for Logger

We also can create Spy for desired Logger, by creating SimpleLogger

    @Spy
    Logger logger1 = new SimpleLogger(Example.class);

    @Spy
    Logger logger2 = new SimpleLogger("org.example.Logger");

Manual bind Mock to Logger

When we don't have possibility to use @ExtendWith(MockitoExtension.class), @RunWith(MockitoJUnitRunner.class) or equivalent method, we can try to start Mockito session manually

class MyTest {

    @Mock
    Logger logger;

    ...

    MockitoSession mockitoSession;    
    
    @Before
    void setup() {
        mockitoSession = Mockito.mockitoSession().initMocks(this).startMocking();
    }
    
    @After
    void cleanup() {
        mockitoSession.finishMocking();
   }

    @Test
    void test() {
        ....
    }
}

or when above is also not possible we must manually bind Mock to Logger.

import org.simplify4u.slf4jmock.LoggerMock;

class MyTest {

    Logger logger;
    
    @Before
    void setup() {
        logger = Mockito.mock(Logger.class);
        LoggerMock.setMock(Example.class, logger);
    }
    
    @After
    void cleanup() {
        LoggerMock.clearMock(Example.class);
    }

    @Test
    void test() {
        ....
    }
}

More examples

Yuo can look at source code tests: