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.
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
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);
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);
...
}
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();
}
}
Related
I have this Configuration class:-
#Configuration
public class AppConfig {
#Autowired
private Environment env;
private List<Weather> weathers = new ArrayList<>();
#Bean
public RestTemplate restTemplate() {
var factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(3000);
return new RestTemplate(factory);
}
#PostConstruct
public void startMonitoring() {
String url = env.getProperty("openWeatherUrl");
String apiKey = env.getProperty("openWeatherApiKey");
try (InputStream resource = app.class.getResourceAsStream("/config.csv")) {
CSVReader reader = new CSVReader(new InputStreamReader(resource));
String[] lineInArray;
while ((lineInArray = reader.readNext()) != null) {
Weather weather = new Weather();
if (!lineInArray[0].contains("city")) {
weather.setCity(lineInArray[0]);
} else {
continue;
}
if (!lineInArray[1].contains("temperaturesLimit")) {
weather.setTemperaturesLimit(Float.parseFloat(lineInArray[2]));
}
if (!lineInArray[2].contains("temperaturesLimit")) {
weather.setFrequency(Integer.parseInt(lineInArray[2]));
}
URI uri = new URI(url + "?q=" + weather.getCity() + "&units=metric&APPID=" + apiKey);
weather.setUri(uri);
weathers.add(weather);
}
} catch (IOException | CsvValidationException | URISyntaxException e) {
System.out.println(e.getMessage());
}
fetchWeatherData();
}
void fetchWeatherData(){
RestTemplate restTemplate = restTemplate();
weathers.forEach(weather -> {
var res = restTemplate.getForEntity(weather.getUri(), Weather.class);
});
}
}
I got error:-
The dependencies of some of the beans in the application context form a cycle:
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
Why is it circular? How do I fix it?
It's quite simple #PostConstruct in AppConfig relies on the RestTemplate bean but this #Bean is part of AppConfig.
This is due to how #Configuration classes work, they are proxied and a call to public RestTemplate restTemplate() would call the proxied method.
A simple fix would be removing the #Bean annotation from your RestTemplate method, if the RestTemplate is not used in other locations.
I am trying to test my Service class that uses Retrofit to make #GET call.
I updated my test case and now I am using the mock-retrofit from square to mock the server following their test sample and some other examples here. But I am struggling to unit test the method that calls the retrofit interface.
The sample example of retrofit-mock tests the interface class. How can I mock the retrofit call for my method that implements the interface method.
public class XmattersApi {
private XmattersInterface service;
public GroupsResponse getGroupsWithSupervisor() throws IOException {
String host = xmattersApiConfiguration.getHost();
Interceptor interceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder().addHeader("Authorization", "TestToken").build();
return chain.proceed(newRequest);
}
};
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.interceptors().add(interceptor);
OkHttpClient client = builder.build();
Retrofit retrofit = new Retrofit.Builder()
.baseurl(getBaseUri(host).toUriString())
.addConverterFactory(JacksonConverterFactory.create())
.client(client)
.build();
service = retrofit.create(XmattersInterface.class);
Call<GroupsResponse> retrofitCall = service.getGroupsWithSupervisor("supervisors");
Response<GroupsResponse> response = retrofitCall.execute();
if (!response.isSuccessful()) {
throw new IOException(response.errorBody() != null
? response.errorBody().string(): "unknown error");
}
return response.body();
}
}
My interface class defined below:
public interface XmattersInterface {
#Headers({"Content-type: application/json;charset=UTF-8"})
#GET("groups")
Call<GroupsResponse> getGroupsWithSupervisor(#Query("embed") String embed);
}
I have tried mocking the retrofit call and returning the mocked response when the service interface is called.
It is not calling the mock. Here is my updated test case.
public class XmattersApiTest {
XmattersApi xmattersAPi;
XmattersInterfaceMock xmattersInterfaceMock;
private final NetworkBehavior behavior = NetworkBehavior.create();
#BeforeEach
public void setUp() throws Exception {
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(JacksonConverterFactory.create())
.baseUrl("http://example.com").build();
MockRetrofit mockRetrofit = new MockRetrofit.Builder(retrofit).networkBehavior(behavior).build();
final BehaviorDelegate<XmattersInterface> delegate = mockRetrofit.create(XmattersInterface.class);
xmattersInterfaceMock = new XmattersInterfaceMock(delegate);
}
#Test
void testGroupsWithSupervisorCase_1() throws IOException {
Call<GroupsResponse> call = mock(Call.class);
XmattersInterface xmattersInterface = mock(XmattersInterface.class)
Call<GroupsResponse> mockCall = xmattersInterfaceMock.getGroupsWithSupervisor("supervisors");
Response<GroupsResponse> mockResponse = mockCall.execute; // Mock data is received here from the XmattersInterfaceMock
when(xmattersInterface.getGroupsWithSupervisor(ArgumentMatchers.anyString())).thenReturn(mockCall);
when(call.execute()).thenReturn(mockResponse)
xmattersApi.getGroupsWithSupervisor(); //Fails with java.net.SocketTimeoutException error
}
}
The service mock class to delegate the request:
public class XmattersInterfaceMock implements XmattersInterface {
private final BehaviorDelegate<XmattersInterface> delegate;
public XmattersInterfaceMock(BehaviorDelegate<XmattersInterface> delegate) {
this.delegate = delegate;
}
#Override
public Call<GroupsResponse> getGroupsWithSupervisor(String embed) {
return delegate.returningResponse(getMockGroupsResponse()).getGroupsWithSupervisor(embed);
}
}
What am I doing wrong here? Someone please help!
I am using Springboot- JUnit, mockito
I am new to JUnit and Mockito. Here I am trying to mock the rest template call and return a response entity. But it throws me a Null pointer Exception. I am not able to figure out what's wrong with the mocking. Can anyone guide me where I am doing wrong?
JUnit5 Unit test cases
class MyTest {
#InjectMocks
private MyService service;
#Mock
private RestTemplate template;
#BeforeEach
void setUp(){
MockitoAnnotations.initMocks(this);
}
private ResponseEntity<String> generateResponse(String body, HttpStatus http) {
return new ResponseEntity<>(body, http);
}
#Test
public void publishEventsuccessResponseTest() {
when(template.postForEntity(ArgumentMatchers.anyString(), ArgumentMatchers.any(),
ArgumentMatchers.<Class<String>>any())).thenReturn(generateResponse("uuid",
HttpStatus.OK));
String result = service.sentData("data");
Assertions.assertTrue("uuid".equals(result));
}
}
Service
class MyService {
public String sentData(String data) {
String jsonResp = "";
ResponseEntity<String> response;
try {
HttpEntity<String> requestBody = new HttpEntity<String>(data, headers);
response = restTemplate.postForEntity("url", requestBody, String.class);
} catch (Exception ex) {}
System.out.println(response); //value : Null
if (response.getStatusCodeValue() == 200) { // Null pointer exception
jsonResp = response.getBody();
}
return jsonResp;
}
}
Change the #BeforeEach to #Before of jUnit4 and add public void setUp()
Your test should be green!
I am trying to mock this method with postForEntity call -
public AuthorizeClient(RestTemplateBuilder builder, Config config) {
this.grantedUrl = config.grantedUrl();
this.restTemplate = HttpClientHelper.getRestTemplate(builder, authorizationConfig);
}
private final RestTemplate restTemplate;
private String grantedUrl;
public List<Permission> getPermissions(
PermissionsRequest permissionsRequest) {
try {
var headers = new HttpHeaders();
var request = new HttpEntity<PermissionsRequest>(permissionsRequest, headers);
var permissions = restTemplate.postForEntity(grantedUrl, request, Permission[].class);
return Arrays.asList(permissions.getBody());
} catch (HttpClientErrorException err) {
logger.error(err);
throw err;
}
}
Here is my test case -
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
Config config = new Config();
#InjectMocks
AuthorizeClient authorizeClient = new AuthorizeClient(restTemplateBuilder, config);
#Mock
private RestTemplate restTemplate;
PermissionsRequest permissionsRequest;
ResponseEntity<Permission[]> expGrantedPermissions;
#Test
public void testAuthorizationPermissions() {
when(restTemplate.postForEntity(anyString(), any(), eq(Permission[].class))).thenReturn(expGrantedPermissions);
var res = authorizeClient.getAllGrantedPermissions(permissionsRequest);
assertNotNull(res);
}
I'm getting this error since url is not mocked properly -
[ERROR] testAuthorizationPermissions Time elapsed: 0.86 s <<< ERROR!
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost/v1/permissions": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)
Now I'm getting this error. Looks like mock is still not observed..
java.lang.IllegalArgumentException: URI is not absolute
At this line -
var res = authorizeClient.getPermissions(permissionsRequest);
My AuthorizeClient is constructed like above..
Please suggest what am I missing. Frankly no clue :(
Thanks in advance
Although the RestTemplate is mocked, it doesn't take effect because the mock doesn't reach the tested object and the method authorizeClient::getAllGrantedPermissions uses its own implementation of RestTemplate.
The RestTemplate to be mocked must be also injected to the implementation which is supposed to use it, otherwise, the original, real, implementation is still used. Make the RestTemplate injectable:
class AuthorizeClient { // You haven't specified the class name
private final RestTemplate restTemplate;
private String grantedUrl;
public AuthorizeClient(RestTemplate restTemplate) { // Dependency injection through constructor
this.restTemplate = restTemplate;
}
public List<Permission> getPermissions(..) { .. }
}
And add #InjectMocks annotation to the tested object which should use mocked dependencies:
#Mock
private RestTemplate restTemplate;
#InjectMock // Injects all necessary #Mock objects
private AuthorizeClient authorizeClient; // An implementation, not an interface
#Test
public void testAuthorizationPermissions() {
Mockito.when(restTemplate.postForEntity(Mockito.anyString(), Mockito.any(), Mockito.any()))
.thenReturn(expGrantedPermissions);
// now it is assured the authorizeClient uses restTemplate and not its own one
var res = authorizeClient.getAllGrantedPermissions(permissionsRequest);
assertNotNull(res);
}
I have what I think is a pretty standard StorageService that exists in a Spring Boot app for a web API.
#Component
#Service
#Slf4j
public class StorageService {
#Autowired
private AmazonS3 s3Client;
#Autowired
private RestTemplate restTemplate;
#Value("${app.aws.s3.bucket}")
private String bucket;
#Async
public boolean fetchAndUpload(List<URI> uris) {
List<CompletableFuture<PutObjectResult>> futures = uris.stream().map(uri ->
fetchAsync(uri).thenApplyAsync((asset) -> put(getName(uri.toString()), asset))
).collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).join();
return true;
}
#Async
private CompletableFuture<InputStream> fetchAsync(URI uri) {
return CompletableFuture.supplyAsync(() -> {
InputStream resp;
try {
// asdf is null here when running my unit tests
Resource asdf = restTemplate.getForObject(uri, Resource.class);
resp = Objects.requireNonNull(asdf).getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
return resp;
});
}
private PutObjectResult put(String name, InputStream data) {
PutObjectRequest request = new PutObjectRequest(bucket, name, data, new ObjectMetadata());
return s3Client.putObject(request);
}
}
Here is an integration test, which at minimum successfully fetches the images given by the integration test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureWebClient
public class StorageServiceIT {
#Value("${app.aws.s3.access.key}")
private String accessKey;
#Value("${app.aws.s3.secret.key")
private String secretKey;
#Spy
private AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
.withRegion(Regions.US_EAST_1)
.build();
#Spy
private RestTemplate restTemplate = new RestTemplateBuilder().build();
#MockBean
private SignService signingService;
#Autowired
private StorageService storageService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void fetchAsync() throws URISyntaxException {
List<URI> uris = List.of(
new URI("https://upload.wikimedia.org/wikipedia/commons/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg"),
new URI("https://upload.wikimedia.org/wikipedia/commons/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg")
);
storageService.fetchAndUpload(uris);
}
}
However, the following unit test does not successfully mock the restTemplate.getForObject call, it constantly returns null, even when setting both arguments to any().
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureWebClient
public class StorageServiceTest {
#MockBean
private AmazonS3 s3Client;
#MockBean
private RestTemplate restTemplate;
#MockBean
private SignService signingService;
#Autowired
// #InjectMocks ???
private StorageService storageService;
#Value("${app.aws.s3.bucket}")
private String bucket;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
List<URI> randomUris(int num) {
final String base = "https://example.com/%s";
return Stream.iterate(0, i -> i)
.limit(num)
.map(o -> URI.create(String.format(base, UUID.randomUUID().toString())))
.collect(Collectors.toList());
}
#Test
public void fetchAsyncTest() {
List<URI> uris = randomUris(2);
uris.forEach(uri -> {
ByteArrayInputStream data = new ByteArrayInputStream(
Integer.toString(uri.hashCode()).getBytes());
PutObjectRequest request = new PutObjectRequest(
bucket, getName(uri.toString()), data, new ObjectMetadata());
Resource getResult = mock(Resource.class);
try {
doReturn(data).when(getResult).getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
doReturn(data).when(restTemplate).getForObject(any(), any());
// none are working
// doReturn(data).when(restTemplate).getForObject(eq(uri), eq(Resource.class));
// doReturn(data).when(restTemplate).getForObject(uri, Resource.class);
// when(restTemplate.getForObject(uri, Resource.class)).thenReturn(data);
// when(restTemplate.getForObject(eq(uri), eq(Resource.class))).thenReturn(data);
// when(restTemplate.getForObject(any(), any())).thenReturn(data);
PutObjectResult res = new PutObjectResult();
doReturn(res).when(s3Client).putObject(eq(request));
// not working here as well, i think
// doReturn(res).when(s3Client).putObject(request);
// doReturn(res).when(s3Client).putObject(any());
// when(s3Client.putObject(eq(request))).thenReturn(res);
// when(s3Client.putObject(request)).thenReturn(res);
// when(s3Client.putObject(any())).thenReturn(res);
});
boolean res = storageService.fetchAndUpload(uris);
}
}
And just in case it's relevant, this is how I'm building the RestTemplate:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
I'm stumped, all advice is appreciated! :|
To follow up on this - my problem was because the method I was attempting to test was marked with Spring Boot's #Async annotation, causing race conditions and some mocks to not be configured properly.