5

Below is a method that I am attempting to write Junit test for:

Method I wish to test:

   //logger declared outside of method in Class
   private static Logger LOGGER = LoggerFactory.getLogger(PersonService.class);

    public void showOutputOfIdentifications(int age) {

        if(age>25){

            LOGGER.info("Over 25");

        }else{

            LOGGER.info("25 or Under");

        }
     }

How can I test to verify that the correct Logback Log statement has been called?

In my tests I have tried to mock the logger and verify that it was called, however this has not worked?

Current Test Attempt:

 @Test
    public void testShowOutputOfIdentifications() throws ParseException{

    int age = 10;

    Logger LOGGER_mock = mock(Logger.class);

    //call method under test
    showOutputOfIdentifications(age);

    verify(LOGGER_mock).info("25 or under");


}

Current failing test output:

Wanted but not invoked:
logger.info(
    "25 or under "
);

Actually, there were zero interactions with this mock.
java123999
  • 6,974
  • 36
  • 77
  • 121
  • 3
    The Class under test probably has a static logger LOGGER that is used. You'd have to inject your mock as that logger. Creating another logger will not get it called. – Fildor Jun 30 '16 at 11:26
  • thanks, please show example of this, I have edited the original method to show my static logger# – java123999 Jun 30 '16 at 11:28
  • The point is: you need to **control** the objects that your "code under test" is using. If you have a factory create an object for you, and that object then gets assigned within your class under test; you have no control. – GhostCat Jun 30 '16 at 11:59

2 Answers2

17

You could add your own appender and assert that the log message was written that way, see Programmatically configure LogBack appender.

Something like this:

// create the mock appender
Appender mockedAppender = Mockito.mock(Appender.class);

// inject it
((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).addAppender(mockedAppender);

// run your test
LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME).error("Test msg");

// verify using ArgumentCaptor
ArgumentCaptor<Appender> argumentCaptor = ArgumentCaptor.forClass(Appender.class);
Mockito.verify(mockedAppender).doAppend(argumentCaptor.capture());

// assert against argumentCaptor.getAllValues()
Assert.assertEquals(1, argumentCaptor.getAllValues().size());
Assert.assertEquals("Test msg", ((LoggingEvent)argumentCaptor.getAllValues().get(0)).getMessage());

// remove the mock appender from static context
((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).detachAppender(mockedAppender);
Tobb
  • 11,850
  • 6
  • 52
  • 77
sebdehne
  • 332
  • 3
  • 7
  • thanks, so I write the appender for the actual logger or the mock? – java123999 Jun 30 '16 at 11:29
  • In a precedent post, I told you to **inject** the mock in the class to test. If you don't no magic will let it be used... – Serge Ballesta Jun 30 '16 at 11:41
  • This unit test changes static resources, meaning that it will change the behavior for all tests running after this. In this case, it will add an appender, and if you have a lot of tests doing this (say in a maven module), you will end up with a lot of appenders. Maybe not a big problem, but I'll edit in code to clean up after the test has been run. – Tobb Jan 25 '18 at 09:51
  • 1
    Why is it `ArgumentCaptor` and not `ArgumentCaptor`? – user3601487 May 13 '19 at 14:44
2

I wrote this using Groovy and Spock to Mock the appenders.

My logback-test.xml file looks like

<configuration debug="true">
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%msg%n</pattern>
    </encoder>
</appender>

<logger name="clicks" level="INFO" additivity="false">
    <appender-ref ref="JSON"/>
</logger>
<logger name="com.carsnip" level="DEBUG"/>

<root level="INFO">
    <appender-ref ref="STDOUT"/>
</root>

And my test contains:

class LogTest extends Specification{
    def "should log"() {
      given:
        Logger root = (Logger) 
        LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        Appender rootMockAppender = Mock()
        root.addAppender(rootMockAppender);
        Logger click = (Logger) LoggerFactory.getLogger("clicks");
        Appender clickMockAppender = Mock()
        click.addAppender(clickMockAppender);

      when:
        click.info("HEYYYYY")

     then:
        0 * rootMockAppender.doAppend(_)
        1 * clickMockAppender.doAppend(_)
    }
}

This is written to check the additivity attribute, so that the ROOT logger, which is higher up in the hierarchy does not pick up the logs from our clicks logger which is going to a different location.