1. 序章

ReflectionTestUtils は、SpringTestContextフレームワークの一部です。 これは、ユニットで使用されるリフレクションベースのユーティリティメソッドのコレクションであり、非公開フィールドを設定し、非公開メソッドを呼び出し、依存関係を注入するための統合テストシナリオです。

このチュートリアルでは、いくつかの例を使用して、単体テストでReflectionTestUtilsを使用する方法を見ていきます。

2. Mavenの依存関係

例に必要なすべての必要な依存関係の最新バージョンをpom.xmlに追加することから始めましょう。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.2.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.2.RELEASE</version>
    <scope>test</scope>
</dependency>

最新のspring-context、spring-test の依存関係は、MavenCentralリポジトリーからダウンロードできます。

3. ReflectionTestUtilsを使用して非パブリックフィールドの値を設定する

単体テストで、パブリックセッターメソッドのないプライベートフィールドを持つクラスのインスタンスを使用する必要があるとします。

それを作成することから始めましょう:

public class Employee {
    private Integer id;
    private String name;

    // standard getters/setters
}

通常、プライベートフィールド id にアクセスして、テスト用の値を割り当てることはできません。これは、パブリックセッターメソッドがないためです。

次に、 ReflectionTestUtils.setField メソッドを使用して、プライベートメンバーidに値を割り当てることができます。

@Test
public void whenNonPublicField_thenReflectionTestUtilsSetField() {
    Employee employee = new Employee();
    ReflectionTestUtils.setField(employee, "id", 1);
 
    assertTrue(employee.getId().equals(1));
}

4. ReflectionTestUtilsを使用して非公開メソッドを呼び出す

ここで、EmployeeクラスにプライベートメソッドemployeeToStringがあると想像してみましょう。

private String employeeToString(){
    return "id: " + getId() + "; name: " + getName();
}

Employee クラスの外部からのアクセスがない場合でも、employeeToStringメソッドの単体テストを次のように記述できます。

@Test
public void whenNonPublicMethod_thenReflectionTestUtilsInvokeMethod() {
    Employee employee = new Employee();
    ReflectionTestUtils.setField(employee, "id", 1);
    employee.setName("Smith, John");
 
    assertTrue(ReflectionTestUtils.invokeMethod(employee, "employeeToString")
      .equals("id: 1; name: Smith, John"));
}

5. ReflectionTestUtilsを使用して依存性を注入する

@Autowiredアノテーションが付いたプライベートフィールドを持つ次のSpringコンポーネントの単体テストを作成するとします。

@Component
public class EmployeeService {
 
    @Autowired
    private HRService hrService;

    public String findEmployeeStatus(Integer employeeId) {
        return "Employee " + employeeId + " status: " + hrService.getEmployeeStatus(employeeId);
    }
}

これで、HRServiceコンポーネントを次のように実装できます。

@Component
public class HRService {

    public String getEmployeeStatus(Integer employeeId) {
        return "Inactive";
    }
}

さらに、 Mockito を使用して、HRServiceクラスのモック実装を作成しましょう。 このモックをEmployeeServiceインスタンスに挿入し、単体テストで使用します。

HRService hrService = mock(HRService.class);
when(hrService.getEmployeeStatus(employee.getId())).thenReturn("Active");

hrService はパブリックセッターのないプライベートフィールドであるため、 ReflectionTestUtils.setField メソッドを使用して、上記で作成したモックをこのプライベートフィールドに挿入します。

EmployeeService employeeService = new EmployeeService();
ReflectionTestUtils.setField(employeeService, "hrService", hrService);

最後に、単体テストは次のようになります。

@Test
public void whenInjectingMockOfDependency_thenReflectionTestUtilsSetField() {
    Employee employee = new Employee();
    ReflectionTestUtils.setField(employee, "id", 1);
    employee.setName("Smith, John");

    HRService hrService = mock(HRService.class);
    when(hrService.getEmployeeStatus(employee.getId())).thenReturn("Active");
    EmployeeService employeeService = new EmployeeService();

    // Inject mock into the private field
    ReflectionTestUtils.setField(employeeService, "hrService", hrService);  

    assertEquals(
      "Employee " + employee.getId() + " status: Active", 
      employeeService.findEmployeeStatus(employee.getId()));
}

この手法は、Beanクラスでフィールドインジェクションを使用しているという事実の回避策であることに注意してください。 コンストラクタインジェクションに切り替えた場合、このアプローチは必要ありません。

6. 結論

このチュートリアルでは、いくつかの例を使用して、単体テストでReflectionTestUtilsを使用する方法を示しました。

コードサンプルは、いつものように、Githubにあります。