背景
SLF4Jなど外部ロギングAPIを用いている時にログの出力のログをテスト時に確認したい。
目的
ログをモック化してログの出力をテストコードで制御できるようにする
コードをざっと確認したい人は"サンプルコード"の章で確認できます。
アプローチ
手順の流れ
- ArgmentCaptor の定義
- 対象のクラスのLoggerを定義し、モックのアペンダーをいれる
- ロガーモックインスタンス指定のコールをキャッチする。
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());
}
}