[リサーチ・メモ]@ParameterizedTestとjsonを使用してパラメータ化テストを行う

## 依存関係を追加
```
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>
  ```
  ※\<scope>test\</scope>にするとjackson-databindがtestでしか使用できなくなりObjectMapperが使えなくなる為注意する

## テスト対象
```
@Service
public class NewMemberRegistrationServiceImpl implements NewMemberRegistrationService {

	@Autowired
	private NewMemberRegistrationMapper newMemberRegistrationMapper;

	@Autowired
	private BCryptPasswordEncoder bCryptPasswordEncoder;

	@Autowired
	private Properties properties;

	/**
	 * 新規会員登録
	 * 
	 * @param memberInfo 会員情報
	 * @throws IOException ファイルから変数取得失敗
	 */
	@Override
	public void newMemberRegistration(MemberInfo memberInfo) throws IOException {

		// 重複チェック
		if (newMemberRegistrationMapper.duplicationCheck(memberInfo.getEmail()) != 0) {
			throw new DuplicationCheckException(memberInfo.getEmail());
		}

		// USER権限を付与
		properties.load(new FileInputStream("src/main/resources/application.properties"));
		String memberAuthority = properties.getProperty("MEMBER_AUTHORITY_USER");
		memberInfo.setAuthority(memberAuthority);

		// パスワードハッシュ化
		memberInfo.setPassword(bCryptPasswordEncoder.encode(memberInfo.getPassword()));

		newMemberRegistrationMapper.newMemberRegistration(memberInfo);
	}

	public class DuplicationCheckException extends RuntimeException {
		public DuplicationCheckException(String email) {
			super("メールアドレスが重複しています: " + email);
		}
	}

}
```

## テストパラメータの準備
```
@Data
public class TestParams {

	// テスト名
	public String testName;

	// リクエストの会員情報
	public MemberInfo memberInfoRequest;

	// 検証結果用の会員情報のレスポンス
	public MemberInfo memberInfoResponse;

	// リクエストのメールアドレス
	public String email;

	// 検証結果用の会員情報のレスポンス
	public int duplicationCheckResponse;

}
```

## テストパラメータのオブジェクト準備
```
[
    {
        "testName": "正常系：新規会員登録チェック",
        "memberInfoRequest": {
            "memberID": null,
            "lastName": "姓",
            "firstName": "名",
            "email": "1234@test.com",
            "phoneNumber": "0123456789",
            "age": "18",
            "gender": "男性",
            "password": "password",
            "authority": "USER",
            "created_at": null,
            "update_at": null
        },
        "memberInfoResponse": {
            "memberID": 3,
            "lastName": "姓",
            "firstName": "名",
            "email": "1234@test.com",
            "phoneNumber": "0123456789",
            "age": "18",
            "gender": "男性",
            "password": "hashedPassword",
            "authority": "USER",
            "created_at": "CURRENT_TIMESTAMP",
            "update_at": null
        },
        "email": "test@test.com",
        "duplicationCheckResponse": 0
    },
    {
        "testName": "正常系：重複チェック　重複なし",
        "memberInfoRequest": null,
        "memberInfoResponse": null,
        "email": "test@test.com",
        "duplicationCheckResponse": 0
    },
    {
        "testName": "正常系：重複チェック　重複あり",
        "memberInfoRequest": null,
        "memberInfoResponse": null,
        "email": "user@test.com",
        "duplicationCheckResponse": 1
    }
]
```


## テストの作成
```
@SpringBootTest
public class NewMemberRegistrationServiceImplTest {

	@InjectMocks
	private NewMemberRegistrationServiceImpl service;

	@Mock
	private NewMemberRegistrationMapper newMemberRegistrationMapper;

	@Mock
	private BCryptPasswordEncoder bCryptPasswordEncoder;

	@Mock
	private Properties properties;

	public static Stream<TestParams> provideTestParameters() throws Exception {
		ObjectMapper objectMapper = new ObjectMapper();
		// JSONファイルのパスを指定
		String jsonFilePath = "テストデータのjsonパス";

		// JSONファイルからTestParamsオブジェクトのリストを読み込む
		List<TestParams> testParamList = objectMapper.readValue(Paths.get(jsonFilePath).toFile(),
				new TypeReference<List<TestParams>>() {
				});

		// リストをStreamに変換して返す
		return testParamList.stream();
	}

	@ParameterizedTest(name = "{0}")
	@MethodSource("provideTestParameters")
	public void testWithParameters(TestParams params) {

		try {
			when(newMemberRegistrationMapper.duplicationCheck(anyString())).thenReturn(params.duplicationCheckResponse);
			when(bCryptPasswordEncoder.encode(anyString())).thenReturn("hashedPassword");

			if (params.testName.equals("エラー系：環境変数ファイルの取得、ファイルの読み込みエラー")) {
				doThrow(new IOException()).when(properties).load(any(FileInputStream.class));
			} else {
				when(properties.getProperty("MEMBER_AUTHORITY_USER")).thenReturn("USER");
			}

			// 検証対象のメソッドを呼び出す
			service.newMemberRegistration(params.memberInfoRequest);

			// newMemberRegistrationMapperリクエストをキャプチャする
			ArgumentCaptor<MemberInfo> memberInfoArgumentCaptor = ArgumentCaptor.forClass(MemberInfo.class);
			verify(newMemberRegistrationMapper).newMemberRegistration(memberInfoArgumentCaptor.capture());

			// newMemberRegistrationMapperリクエストの整合性を検証する
			MemberInfo capturedMemberInfo = memberInfoArgumentCaptor.getValue();
			assertEquals(params.memberInfoResponse, capturedMemberInfo);

			// 例外が発生するテストケースがオーバーランしないことを確認する
			if (params.resultExceptionName != null) {
				fail("例外が発生していない");
			}
		} catch (Exception e) {
			// キャッチした例外の検証
			if (params.resultExceptionName != null) {

				// 例外が発生するテストケースで想定した例外が発生したことを確認する
				assertEquals(e.getClass().getName(), (params.resultExceptionName));
			} else {
				// 例外が発生しないテストケースで想定外の例外が発生しないことを確認する
				fail("例外が発生している");
			}
		}

	}
}
```

|アノテーション|内容|
|:--:|:--:|
|`@SpringBootTest`|DIを動作させる為に付与する|
|`@InjectMocks`|テスト対象に付与する|
|`@Mock`|モック化する為に、テスト対象外に付与する|
|`@ParameterizedTest(name = "{0}")`|異なるjsonパラメータを使用して同じテストを行う為に付与する<br>junit実行後にテスト名を表示させる為にnameを指定する<br>※実行結果参照|
|`@MethodSource("provideTestParameters")`|上記とセットで使用し、引数にjsonデータを返すメソッドを指定する|

※実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1559909/0a317222-08ee-41c8-9f66-2d0da41810f4.png)