mockitoでArgumentCaptorを使い、引数を検証する

先日、単体テストについて学習したときに、あるメソッドに渡された引数を検証したい場合がありまして、その際にArgumentCaptorを使用したので記事にしてみます。

#概要
単体テストをしているときに、例えばスタブ化したメソッドがちゃんと呼ばれたか、あるいはそのメソッドに対して適切に引数が渡されたかを検証したい場合があります。そういうときに私はArgumentCaptorを使用しました。

#引数の数が一つの場合
・まずはモック化したオブジェクトのメソッドに対して渡す引数が一つである場合について記載します。
・最初に、テストを行うクラスについて記載します。

```java:PersonItemWriter.java
public class PersonItemWriter implements ItemWriter<Object>{

	@Autowired
	PersonService service;

	@Override
	public void write(List<? extends Object> items) throws Exception {
		for(Object person : items) {
			service.updatePerson((Person)person);
		}

	}

}
```
・こちらのクラスは「[Spring Batchで勉強したことまとめ](https://qiita.com/kyabetsuda/items/f011533621cff7f53c63)」で少しだけ紹介しました、Writerクラスについて私が実装したものです。
・パラメータとして渡されたオブジェクトのリストを、サービスクラスを用いてデータベースに登録するだけの簡単な処理となっています。
・今回のテストではこのクラスをテストし、その際にPersonServiceはまだ完成していないのでモック化するという設定です。
・PersonServiceは以下のようなものであると仮定してください。

```java:PersonService.java
public interface PersonService {

	// データベースへの登録を行う処理
	public void updatePerson(Person person);

}
```

・以下はテストクラスです。

```java:PersonItemWriterTest.java
public class PersonItemWriterTest {

	@Mock
	PersonService personService;

	@InjectMocks
	private PersonItemWriter writer = new PersonItemWriter();

	@Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

	@Test
	public void testParameter() throws Exception {
		ArgumentCaptor<Person> personCaptor = ArgumentCaptor.forClass(Person.class);
		List<Person> people = new ArrayList<Person>();
		for(int i = 0; i < 3; i++) {
			Person person = new Person(i, "hoge" + i, "fuga" + i);
			people.add(person);
		}

		// テスト対象の実行
		writer.write(people);

		verify(personService, times(3)).updatePerson(personCaptor.capture());
		Person person = personCaptor.getValue();
		assertThat(person, is(people.get(people.size() - 1)));
	}


}
```
・Mockアノテーションを使用してPersonServiceをモック化しています(mockitoの使い方につきましては「参考」に記載のサイトを参照しました)。
・引数を検証する際にはArgumentCaptorのインスタンスを用意します。
・インスタンスかするときにクラスを指定できますが、その際はテストしたいメソッドのパラメータが取りうるクラスを指定します。
・今回のテストではPersonService.updatePerson()がちゃんと呼ばれたかを検証したいです。
・ですので、ArgumentCaptorに指定するクラスはPersonです。
・メソッドが呼ばれたかどうかを検証するメソッドはMockitoのverify()です。
・verifyで指定したメソッドの引数にArgumentCaptorを渡します。
・verifyでPersonItemWriter.write()でPersonService.updatePerson()が呼ばれたかどうかを検証し、かつArgumentCaptor.getValue()を使用することで引数が適切だったかどうかを検証します。

#引数が複数ある場合
・実行したかどうかを検証したいメソッドの引数が複数である場合もあると思います。
・例えばPersonServiceのメソッドが以下のような場合です。

```java:PersonService.java
public interface PersonService {

	public void updatePersonById(int id, String lastName, String firstName);

}
```

・PersonItemWriterではPersonServiceを以下のように用いています。

```java:PersonItemWriter.java
public class PersonItemWriter implements ItemWriter<Object>{

	@Autowired
	PersonService service;

	@Override
	public void write(List<? extends Object> items) throws Exception {
		for(Object person : items) {
			service.updatePersonById(((Person) person).getId(), ((Person) person).getLastName(), ((Person) person).getFirstName());
		}

	}

}
```

・PersonItemWriter.write()を検証する際にPersonService.updatePersonById()に渡された引数を検証したい場合には以下のようにテストを記述します。

```java:PersonItemWriterTest.java
public class PersonItemWriterTest {

	@Mock
	PersonService personService;

	@InjectMocks
	private PersonItemWriter writer = new PersonItemWriter();

	@Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

	@Test
	public void testParameter() throws Exception {
		ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
		ArgumentCaptor<String> lastNameCaptor = ArgumentCaptor.forClass(String.class);
		ArgumentCaptor<String> firstNameCaptor = ArgumentCaptor.forClass(String.class);
		List<Person> people = new ArrayList<Person>();
		for(int i = 0; i < 3; i++) {
			Person person = new Person(i, "hoge" + i, "fuga" + i);
			people.add(person);
		}

		// テスト対象の実行
		writer.write(people);

		verify(personService, times(3)).updatePersonById(idCaptor.capture(), lastNameCaptor.capture(), firstNameCaptor.capture());
		Integer id = idCaptor.getValue();
		String lastName = lastNameCaptor.getValue();
		String firstName = firstNameCaptor.getValue();
		assertThat(id, is(people.get(people.size() - 1).getId()));
		assertThat(lastName, is(people.get(people.size() - 1).getLastName()));
		assertThat(firstName, is(people.get(people.size() - 1).getFirstName()));

	}
}
```
・複数の引数に合わせて複数のArgumentCaptorを用意します。
・ArgumentCaptorに設定するクラスは必ず「参照型」のクラスである必要があるため、プリミティブ型を引数に取っている場合はラッパークラス(ここではintに対してのInteger)を設定します。
・引数が単数である場合と同様に、verify()でメソッドを検証する際の引数としてArgumentCaptor.capture()をそれぞれの引数に渡します。
・最後に、引数に渡した3個のArgumentCaptorそれぞれからgetValue()で値を取得して、引数に渡された値の検証を行なっています。

ArgumentCaptorを使った引数の検証方法の説明は以上になります。

参考：
[ArgumentCaptorによる引数の検証](https://riptutorial.com/ja/mockito/example/16192/argumentcaptorによる引数の検証)
[Mockito事始め](http://niwaka.hateblo.jp/entry/2015/09/19/171610)
[Junitライブラリ「Mockito」のverifyの使い方](http://javatechnology.net/java/mockito-verify/)
[心地良すぎるモックライブラリ Mockito　〜その1〜](http://etc9.hatenablog.com/entry/20101108/1289218176)
[JUnit備忘録](https://qiita.com/rs_/items/ad1dbdbd439728a1ed97)