I'm trying to verify a logger message with mockito.
However, I can not run my junit class to coverage all lines of code.
Do you know the reason why?
My code:
public class App {
private static final Logger LOGGER = Logger.getLogger(App.class);
public List<String> addToListIfSizeIsUnder3(final List<String> list, final String value) {
if (list == null) {
LOGGER.error("A null list was passed in");
return null;
}
if (list.size() < 3) {
list.add(value);
} else {
LOGGER.debug("The list already has {} entries"+ list.size());
}
return list;
}
}
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class AppTest {
private App uut;
@Mock
private Appender mockAppender;
@Captor
private ArgumentCaptor<LoggingEvent> captorLoggingEvent;
@Before
public void setup() {
uut = new App();
Logger root = Logger.getRootLogger();
root.addAppender(mockAppender);
root.setLevel(Level.INFO);
}
/**
* I want to test with over 3 elements.
*/
@Test
public void testWithOver3Element() {
List<String> myList = new ArrayList<String>();
myList.add("value 1");
myList.add("value 2");
myList.add("value 3");
myList.add("value 4");
List<String> outputList = uut.addToListIfSizeIsUnder3(myList, "some value");
Assert.assertEquals(4, outputList.size());
Assert.assertFalse(myList.contains("some value"));
try {
verify(mockAppender, times(1)).doAppend(captorLoggingEvent.capture());
} catch (AssertionError e) {
e.printStackTrace();
}
LoggingEvent loggingEvent = captorLoggingEvent.getAllValues().get(0);
Assert.assertEquals("The list already has {} entries", loggingEvent.getMessage());
Assert.assertEquals(Level.DEBUG, loggingEvent.getLevel());
}
}
ERROR:
Wanted but not invoked: mockAppender.doAppend(); -> at AppTest.testWithOver3Element(AppTest.java:52) Actually, there were zero interactions with this mock.
at AppTest.testWithOver3Element(AppTest.java:52) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37) at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Mockito allows us to create mock objects. Since static method belongs to the class, there is no way in Mockito to mock static methods. However, we can use PowerMock along with Mockito framework to mock static methods.
ALL: in unit tests you can change the log level of the logger, to verify that when a certain log level is set, certain messages will no longer be logged. For example, if the log level were to be set to WARN, then the FruitLogger should no longer log the message body.
We can use Mockito class mock() method to create a mock object of a given class or interface. This is the simplest way to mock an object. We are using JUnit 5 to write test cases in conjunction with Mockito to mock objects.
Although a valid answer has already been provided, I want to propose an alternative where you don't need to expose the Logger for mocking and you don't need to use mocking at all. It works for both SLF4J API and Log4J2 API.
See here for the library https://github.com/Hakky54/log-captor
Include in your maven file the reference for the library:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>logcaptor</artifactId>
<version>2.6.1</version>
<scope>test</scope>
</dependency>
In java code test method you should include this:
LogCaptor logCaptor = LogCaptor.forClass(App.class);
// do the test logic....
assertThat(logCaptor.getLogs()).contains("Some log to assert");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With