我有一些正在测试的代码,它调用Java记录器来报告其状态。 在JUnit测试代码中,我想验证在这个日志记录器中创建了正确的日志条目。大致如下:

methodUnderTest(bool x){
    if(x)
        logger.info("x happened")
}

@Test tester(){
    // perhaps setup a logger first.
    methodUnderTest(true);
    assertXXXXXX(loggedLevel(),Level.INFO);
}

我认为这可以用一个经过特别调整的记录器(或处理程序或格式化程序)来完成,但我更愿意重用现有的解决方案。(而且,老实说,我不清楚如何从记录器获得logRecord,但假设这是可能的。)


当前回答

检查这个库https://github.com/Hakky54/log-captor

在maven文件中包含库的引用:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>logcaptor</artifactId>
    <version>2.5.0</version>
    <scope>test</scope>
</dependency>

在java代码测试方法中,你应该包括以下内容:

LogCaptor logCaptor = LogCaptor.forClass(MyClass.class);

 // do the test logic....

assertThat(logCaptor.getLogs()).contains("Some log to assert");

其他回答

受到@RonaldBlaschke的解决方案的启发,我想到了这个:

public class Log4JTester extends ExternalResource {
    TestAppender appender;

    @Override
    protected void before() {
        appender = new TestAppender();
        final Logger rootLogger = Logger.getRootLogger();
        rootLogger.addAppender(appender);
    }

    @Override
    protected void after() {
        final Logger rootLogger = Logger.getRootLogger();
        rootLogger.removeAppender(appender);
    }

    public void assertLogged(Matcher<String> matcher) {
        for(LoggingEvent event : appender.events) {
            if(matcher.matches(event.getMessage())) {
                return;
            }
        }
        fail("No event matches " + matcher);
    }

    private static class TestAppender extends AppenderSkeleton {

        List<LoggingEvent> events = new ArrayList<LoggingEvent>();

        @Override
        protected void append(LoggingEvent event) {
            events.add(event);
        }

        @Override
        public void close() {

        }

        @Override
        public boolean requiresLayout() {
            return false;
        }
    }

}

... 这允许你做:

@Rule public Log4JTester logTest = new Log4JTester();

@Test
public void testFoo() {
     user.setStatus(Status.PREMIUM);
     logTest.assertLogged(
        stringContains("Note added to account: premium customer"));
}

你也许可以用更聪明的方式来使用hamcrest,但我就讲到这里。

实际上,您是在测试依赖类的副作用。对于单元测试,您只需要验证这一点

logger.info()

使用正确的参数调用。因此,使用一个模拟框架来模拟记录器,这将允许您测试自己类的行为。

简单的方法

  @ExtendWith(OutputCaptureExtension.class)
  class MyTestClass { 
    
          @Test
          void my_test_method(CapturedOutput output) {
               assertThat(output).contains("my test log.");
          }
  }

在类实现中不需要依赖硬编码的静态全局记录器,可以在默认构造函数中提供默认记录器,然后使用特定的构造函数设置对所提供的记录器的引用。

class MyClassToTest {
    private final Logger logger;
    
    public MyClassToTest() {
      this(SomeStatic.logger);
    };
    
    MyClassToTest(Logger logger) {
      this.logger = logger;
    };
    
    public void someOperation() {
        logger.warn("warning message");
        // ...
    };
};

class MyClassToTestTest {
    
    @Test
    public warnCalled() {
        Logger loggerMock = mock(Logger.class);
        MyClassTest myClassToTest = new MyClassToTest(logger);
        myClassToTest.someOperation();
        verify(loggerMock).warn(anyString());
    };
}

模拟Appender可以帮助捕获日志行。 在http://clearqa.blogspot.co.uk/2016/12/test-log-lines.html上找到示例

// Fully working test at: https://github.com/njaiswal/logLineTester/blob/master/src/test/java/com/nj/Utils/UtilsTest.java

@Test
public void testUtilsLog() throws InterruptedException {

    Logger utilsLogger = (Logger) LoggerFactory.getLogger("com.nj.utils");

    final Appender mockAppender = mock(Appender.class);
    when(mockAppender.getName()).thenReturn("MOCK");
    utilsLogger.addAppender(mockAppender);

    final List<String> capturedLogs = Collections.synchronizedList(new ArrayList<>());
    final CountDownLatch latch = new CountDownLatch(3);

    //Capture logs
    doAnswer((invocation) -> {
        LoggingEvent loggingEvent = invocation.getArgumentAt(0, LoggingEvent.class);
        capturedLogs.add(loggingEvent.getFormattedMessage());
        latch.countDown();
        return null;
    }).when(mockAppender).doAppend(any());

    //Call method which will do logging to be tested
    Application.main(null);

    //Wait 5 seconds for latch to be true. That means 3 log lines were logged
    assertThat(latch.await(5L, TimeUnit.SECONDS), is(true));

    //Now assert the captured logs
    assertThat(capturedLogs, hasItem(containsString("One")));
    assertThat(capturedLogs, hasItem(containsString("Two")));
    assertThat(capturedLogs, hasItem(containsString("Three")));
}