mkarki
mkarki

Reputation: 59

Mock Test for AWS Secret Manager - Java

I am trying to test the AWS SecretManager call using Mockito but when I run the program, I am getting the Null Pointer Exception.

@ExtendWith(MockitoExtension.class)
class XXXX{

String secret = "{ \"client_id\": \"XXXXXX\",\"client_secret\": \"XXXXXX\"} ";
    
    @Mock
    AWSSecretsManager secretsClient;
    @Mock
    GetSecretValueRequest secretValueRequest;
    @Mock
    GetSecretValueResult secretValueResult;

  @BeforeEach
  public void setUp(){
    lenient().when(secretsClient.getSecretValue(secretValueRequest)).thenReturn(secretValueResult);
    lenient().when(secretValueResult.getSecretString()).thenReturn(secret);
  }

}

Here, when I am running, I am getting the NullPointerException at when(secretsClient.getSecretValue(secretValueRequest)). It says as secretsClient.getSecretValue(secretValueRequest) is null which is passed as parameter to when(). Any suggestion or advice what I am doing wrong here, please.

Upvotes: 2

Views: 11809

Answers (4)

ohrlando
ohrlando

Reputation: 136

solution is create real GetSecretValueResponse:

GetSecretValueResponse response = GetSecretValueResponse.builder().secretString(secretValue).build();

so my test is:

public class AWSSecretsManagerTest {
    @InjectMock SecretsManagerClient client;

    @Inject AWSSecretsManager secretsManager;

    @Test
    void getSecret_GetSecretStringByName() {
        // FIXTURE
        var secretValue = "some-value";
        GetSecretValueResponse response = GetSecretValueResponse.builder().secretString(secretValue).build();
        when(client.getSecretValue((GetSecretValueRequest) any())).thenReturn(response);
        // exercise
        var result = secretsManager.getSecret("some-secret");
        //verify
        Assertions.assertEquals(secretValue, result);
    }
}

my manager:

@ApplicationScoped
public class AWSSecretsManager implements SecretsManager {
    public static final String VERSION_STAGE = "AWSCURRENT";

    @Inject
    SecretsManagerClient secretsManagerClient;

    private GetSecretValueRequest generateGetSecretValueRequest(String secretName) {
        return GetSecretValueRequest.builder()
                .secretId(secretName)
                .versionStage(VERSION_STAGE)
                .build();
    }

    public String getSecret(String secretName) {
        return secretsManagerClient.getSecretValue(generateGetSecretValueRequest(secretName)).secretString();
    }
}

Upvotes: 0

henry sneed
henry sneed

Reputation: 526

I would determine how you are building your AWSSecretsManager instance within your getSecret() method.

Consider if you are using a getSecret() method similar to the one AWS provides like the following:

public static void getSecret() {
    String secretName = "arn:aws:secretsmanager:us-east-1:xxxxxxx";
    String region = "us-east-1";

    // Create a Secrets Manager client
    AWSSecretsManager client  = AWSSecretsManagerClientBuilder.standard()
                                    .withRegion(region)
                                    .build();
    
    GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest()
        .withSecretId(secretName);
    GetSecretValueResult getSecretValueResult;

    try {
      getSecretValueResult = client.getSecretValue(getSecretValueRequest);
    } catch (Exception e) {
      logger.error("Error retrieving secret: {0}", e);
      throw e;
    }
    ...
}

In this case, mocking AWSSecretsManager within your JUnit test will not have the desired outcome because the getSecret() method is instantiating AWSSecretsManagerClientBuilder and assigning it to client each time getSecret() is called. Instead, you can add a configuration class with an AWSSecretsManager bean as and then autowire it in the constructor of the class that contains the getSecret() method.

Add Configuration

@Configuration
public class Config {

  @Value("${cloud.aws.region.static}")
  private String region;

  @Bean
  public AWSSecretsManager awsSecretsManager(String region) {
    return AWSSecretsManagerClientBuilder.standard()
        .withRegion(region)
        .build();
  }
}

Update getSecret()

After doing so, your method should look more like this

 private String getSecret() {
    GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest()
        .withSecretId(secretName);
    GetSecretValueResult getSecretValueResult;

    try {
      getSecretValueResult = client.getSecretValue(getSecretValueRequest);
    } catch (Exception e) {
      logger.error("Error retrieving secret: {0}", e);
      throw e;
    }
    ...
  } 
    

Test

Now, you will be able to mock the AWSSecretsManager as intended:

  @Mock
  AWSSecretsManager client;

  private final YourClass undertest;

  @BeforeEach
  void setUp() {
    MockitoAnnotations.openMocks(this);
    undertest = new YourClass(...)
  }
  
  @Test
  void testYourClass() {
    GetSecretValueResult expected = new GetSecretValueResult();
    expected.setSecretString("{\"client_id\": \"XXXXXX\",\"client_secret\": \"XXXXXX\"}"); 
    when(client.getSecretValue(any(GetSecretValueRequest.class)))
        .thenReturn(expected);
    ...
}

Upvotes: 0

Todd
Todd

Reputation: 11

Have you tried setting the value on the GetSecretVaueResult first then returning it; something like this?

@Mock
AWSSecretsManager secretsClient;
GetSecretValueResult secretValueResult = new GetSecretValueResult();
secretValueResult.setSecretString("{\"client_id\": \"XXXXXX\",\"client_secret\": \"XXXXXX\"}");

when(secretsClient.getSecretValue(any(GetSecretValueRequest.class))).thenReturn(secretValueResult);

Upvotes: 1

Matthew
Matthew

Reputation: 11347

You need to install the mockito extension via: @ExtendWith(MockitoExtension.class)

(I think you'll need to make the member variables non-private too).

More clues here: https://www.baeldung.com/mockito-junit-5-extension

Upvotes: 2

Related Questions