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

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

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

コードをざっと確認したい人は"[サンプルコード](https://qiita.com/yusha3/items/c9576bbe8b6cc5b27897#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89)"の章で確認できます。

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

## 1. ArgmentCaptor の定義

``` java
  @Captor
  private ArgumentCaptor<LoggingEvent> captorLoggingEvent;
``` 

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


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

``` java 
	  @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. ロガーモックインスタンス指定のコールをキャッチする

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

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


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

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

# サンプルコード
### メインクラス

``` java

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.");
  }
}
```


### テストクラス

``` java

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());
  }
}

```

# 参考資料

- [Logback- Appenders](http://logback.qos.ch/manual/appenders_ja.html)
- [[Java][Spring] ロガーの挙動をテストする](https://qiita.com/ishiitakeru/items/e71078f2b87ee5022f19)