6
6

More than 1 year has passed since last update.

SLF4Jなど外部ロギングAPIを用いている時にログの出力のロガーの挙動をテストする

Last updated at Posted at 2020-09-11

背景

SLF4Jなど外部ロギングAPIを用いている時にログの出力のログをテスト時に確認したい。

目的

ログをモック化してログの出力をテストコードで制御できるようにする

コードをざっと確認したい人は"サンプルコード"の章で確認できます。

アプローチ

手順の流れ
1. ArgmentCaptor の定義
2. 対象のクラスのLoggerを定義し、モックのアペンダーをいれる
3. ロガーモックインスタンス指定のコールをキャッチする。

1. ArgmentCaptor の定義

  @Captor
  private ArgumentCaptor<LoggingEvent> captorLoggingEvent;

ArgmentCaptor はmockito のクラスでスタブ化したメソッドがちゃんと呼ばれたか、あるいはそのメソッドに対して適切な引数が渡されたか検証したい場合に用いる。
今回ではログ出力の型であるLoggingEvent の引数をキャプチャする。

2. 対象のクラスのLoggerを定義し、モックのアペンダーをいれる

      @Mock
      private Appender mockAppender;

      final Logger logger = (Logger) LoggerFactory.getLogger([テスト対象のクラス名].class);
      logger.addAppender(mockAppender);

SLF4Jを用いずに独自にlogger 変数を定義し、logger にモックしたAppender を加える。
Appender はログの出力先の設定を行うために使用されるインターフェイス。
Appender は addAppender() メソッドでLogger にセットすることができる。
ここでは、モックAppender を用意して、それをaddAppender() することでlogger のログ出力先をモックにすることができる。

3. ロガーモックインスタンス指定のコールをキャッチする

     verify(mockAppender).doAppend(captorLoggingEvent.capture());
     assertEquals("INFO", captorLoggingEvent.getValue().getLevel().toString());

doAppend はイベントオブジェクトを引数に持つ。
append されたAppender がロギングする役割

上記の方法では最後のログしかキャッチできない。すべてのログをキャッチするためにはリストに代入する。

// アプリケーションログのみを抽出
      List<LoggingEvent> events = captorLoggingEvent.getAllValues().stream()                                                         
                                  .collect(Collectors.toList());

サンプルコード

メインクラス


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class Sample {

  public void doLogging() {
    log.info("sample");
    log.info("sample access_log.");
    log.info("sample access_log 2.");
  }
}

テストクラス


import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class SampleTest {

  @Captor
  private ArgumentCaptor<LoggingEvent> captorLoggingEvent;

  @Mock
  private Appender mockAppender;

  @InjectMocks
  @Autowired
  Sample sample;

  @BeforeEach
  void setUp() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  void test() throws Exception {
    Logger logger = (Logger) LoggerFactory.getLogger(Sample.class);
    logger.addAppender(mockAppender);

    sample.doLogging();

    verify(mockAppender).doAppend(captorLoggingEvent.capture());
    // INFO レベルのログが出力されているか確認
    assertEquals("INFO", captorLoggingEvent.getValue().getLevel().toString());

    // アプリケーションログのみを抽出
    List<LoggingEvent> events = captorLoggingEvent.getAllValues().stream()
        .filter(e -> e.getMessage().contains("access_log"))
        .collect(Collectors.toList());

    assertEquals(2, events.size());
  }
}

参考資料

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6