Introduction
Yet another way to testing logging in application which use SLF4J
.
Features
- this binding support for easy create
Mockito
mock forLogger
- call to
Logger
can be delegated to instance ofSimpleLogger
, so we can create standardsimplelogger.properties
- support for testing and mocking
MDC
- light transitive dependencies - only
slf4j-api
andmockito-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!
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.4.0</version>
<scope>test</scope>
</dependency>
For slf4j-api
2.0.?? and later.
<dependency>
<groupId>org.simplify4u</groupId>
<artifactId>slf4j2-mock</artifactId>
<version>2.4.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: