Quarkus(Java)のテストで@Injectが上手く行かなかった時は@QuarkusTestで大体どうにかなる

# はじめに
JavaのAWS SDKを使って、cognitoの情報を取得するコードを書いて、テストで動作確認しようとしたら詰まった。依存関係が深くなってくると、作成したインスタンス内の```@Inject```が適応されないらしい。```@QuarkusTest```を使えば、ランタイム環境まで模倣されるので複雑な依存関係もちゃんと動くことが分かった。

# 上手く行ったコード
**プロジェクト環境**
Java: 17.0.9
Quarkus: 3.2.7.Final
Gradle: 7.6.3

cognitoユーザープールに登録されているユーザーの一覧を取得してみる。

```java
// /config/AWSClientConfig.java

import io.quarkus.runtime.annotations.StaticInitSafe;
import io.smallrye.config.ConfigMapping;

@StaticInitSafe
@ConfigMapping(prefix = "aws.credentials")
public interface AWSClientConfig {
    String accessKey();
    String secretKey();
}
```

```java
// /config/CognitoClientConfig.java

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
import software.amazon.awssdk.regions.Region;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class CognitoClientConfig {

    @Inject
    public AWSClientConfig config;

    public CognitoIdentityProviderClient getCognitoClient(){
        return CognitoIdentityProviderClient.builder()
                    .region(Region.AP_NORTHEAST_1)
                    .credentialsProvider(StaticCredentialsProvider.create(
                            AwsBasicCredentials.create(config.accessKey(), config.secretKey())))
                    .build();
    }
}

```

複数コードで呼び出すからconfigフォルダに設定類を記載。```AWSClientConfig```と```CognitoClientConfig```で分けるのは必須ではないが、他サービスも使うなら
```AWSClientConfig```を継承する形が分かりやすい。

```java
// /main/UserService.java

import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
import software.amazon.awssdk.services.cognitoidentityprovider.model.*;

import your.project.config.AWSClientConfig;
import your.project.config.CognitoClientConfig;
import your.project.data.user.output.FindAllUserOutput;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class UserService {
    @Inject
    CognitoClientConfig cognitoClientConfig; // コンフィグ情報をインジェクト

    public FindAllUserOutput FindAll() {
        FindAllUserOutput output = new FindAllUserOutput();
        CognitoIdentityProviderClient cognitoClient = cognitoClientConfig.getCognitoClient();

        try {
            ListUsersRequest request = ListUsersRequest.builder()
                .userPoolId(YOUR_USER_POOL_ID)
                .build();

            ListUsersResponse response = cognitoClient.listUsers(request);
            response.users().forEach(user -> {
                String userName = user.username();
                String emailAddress = user.attributes().stream() //他属性があれば追加
                    .filter(attribute -> attribute.name().equals("email"))
                    .findFirst()
                    .map(AttributeType::value)
                    .orElse("メールアドレスが見つかりません");
            
                System.out.println("User name: " + userName + ", Email address: " + emailAddress);
            });

        } catch (CognitoIdentityProviderException e){
            System.err.println(e.awsErrorDetails().errorMessage());
        } catch (Exception e) {
            System.err.println("エラーが発生しました: " + e.getMessage());
        }
        return output;
    }
}
```

```java
// /test/UserServiceTest.java

import org.junit.jupiter.api.Test;
import jakarta.inject.Inject;
import io.quarkus.test.junit.QuarkusTest;

import your.project.data.user.output.FindAllUserOutput;

@QuarkusTest // ここでランタイムを模倣
public class UserServiceTest {
    @Inject
    private UserService userService;

    @Test
    void testListAgent () {
        FindAllUserOutput output = userService.FindAll();
    }
}
```

# ハマりポイント
最初はそのままテストコードで```Service```クラスを```@Inject```して、```new```でインスタンスを新規作成を試みたが、その後のservice内のメソッドが呼び出せなかった。原因は依存しているコンフィグがnullであったこと。
```
java.lang.NullPointerException: Cannot invoke "your.project.config.AWSClientConfig.accessKey()" because "this.config" is null
```

振り返ってみると、今回のコードは以下のような依存関係にあった。
```UserService.java```
↓依存
```CognitoClientConfig.java```
↓依存
```AWSClientConfig.java```

どうやら、Java標準の```new```を使ってインスタンスを作成すると、そのインスタンスはQuarkusのランタイムコンテキストの外で作成されるため、依存性注入が行われないらしい。つまり、newキーワードで作成されたオブジェクトは、その内部で```@Inject```や他のQuarkusの機能を使用することができないことが分かった。

そこで```@QuarkusTest```を使用すると、Quarkusアプリケーションのコンポーネントやサービスをテストする際に、QuarkusのDI機能がサポートされるようになる。

副作用というほどでもないが、```@QuarkusTest```を使用するとQuarkusのランタイム環境を模倣されるため、DBの接続や他のクレデンシャルを必要とするコードがあると、設定が少し面倒になる。