How to unit test retrofit web API calls - java

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

Related

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.
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();
}
}

Provide different ClientInterceptor per request using Spring Web Services

I've created a custom web service client by extending WebServiceGatewaySupport and also implement custom ClientInterceptor to log some request/response data.
I have to create new interceptor for every call because it has to store some data about the request.
The problem occurs when I make two or more calls to my client. The first request applies its own interceptor with its clientId. The second should do the same. But since both requests use the same WebServicetemplate in my client, the second request replaces the interceptor with its own, with its clientId there.
As a result, I should get the following output to the console:
Request: clientId-1
Request: clientId-2
Response: clientId-1
Response: clientId-2
But I got this:
Request: clientId-1
Request: clientId-2
Response: clientId-2
Response: clientId-2
Here is come code examples (just for understanding how it should work):
#Data
class Response {
private final String result;
public Response(String result) {
this.result = result;
}
}
#Data
class Request {
private final String firstName;
private final String lastName;
}
#Data
class Context {
private final String clientId;
}
#Data
class Client {
private final String clientId;
private final String firstName;
private final String lastName;
}
class CustomInterceptor extends ClientInterceptorAdapter {
private final String clientId;
public CustomInterceptor(String clientId) {
this.clientId = clientId;
}
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Request: " + clientId);
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Response: " + clientId);
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Error: " + clientId);
return true;
}
}
#Component
class CustomClient extends WebServiceGatewaySupport {
public Response sendRequest(Request request, Context context) {
CustomInterceptor[] interceptors = {new CustomInterceptor(context.getClientId())};
setInterceptors(interceptors);
return (Response) getWebServiceTemplate().marshalSendAndReceive(request);
}
}
#Service
#RequiredArgsConstructor
class CustomService {
private final CustomClient customClient;
public String call(Request request, Context context) {
Response response = customClient.sendRequest(request, context);
return response.getResult();
}
}
#RestController
#RequestMapping("/test")
#RequiredArgsConstructor
class CustomController {
private final CustomService service;
public CustomController(CustomService service) {
this.service = service;
}
#PostMapping
public String test(#RequestBody Client client) {
Request request = new Request(client.getFirstName(), client.getLastName());
Context context = new Context(client.getClientId());
return service.call(request, context);
}
}
Is it possible to implement custom interceptors with some state for each call? Preferably without any locks on WebServicetemplate to avoid performance degradation.
Okay. I've found the solution for my case.
I've created an implementation of WebServiceMessageCallback and using it I'm saving data of each request not in interceptor but in WebServiceMessage's mime header.
#Data
class CustomMessageCallback implements WebServiceMessageCallback {
private final String clientId;
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
MimeHeaders headers = ((SaajSoapMessage) message).getSaajMessage().getMimeHeaders();
headers.addHeader("X-Client-Id", clientId);
}
}
And pass this callback in my client implementation:
#Component
class CustomClient extends WebServiceGatewaySupport {
public Response sendRequest(Request request, Context context) {
CustomInterceptor[] interceptors = {new CustomInterceptor()};
setInterceptors(interceptors);
return (Response) getWebServiceTemplate()
.marshalSendAndReceive(request, new CustomMessageCallback(context.getClientId()));
}
}
So now I can get this data while processing request/response/error via interceptor.
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String clientId = ((SaajSoapMessage) messageContext.getRequest())
.getSaajMessage()
.getMimeHeaders()
.getHeader("X-Client-Id")[0];
System.out.println("Request: " + clientId);
return true;
}

Dagger 2 and Retrofit 2 change base URL

In my case, I need to change URL dynamically, but I don't want to create 2 instances of the retrofit client. I'm trying to change base URL via interceptor modifications, but retrofit still uses the old value. What am I doing wrong?
App.java
public class App extends Application {
private static AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent =
DaggerAppComponent
.builder()
.appModule(new AppModule(this))
.build();
}
#NonNull
public static App get(#NonNull Context context) {
return (App) context.getApplicationContext();
}
public static AppComponent getAppComponent() {
return appComponent;
}
}
AppModule.java
#Module
public class AppModule {
private Context context;
public AppModule(Context context) {
this.context = context;
}
#Provides
#Singleton
Context provideContext() {
return context;
}
}
NetModule.java
#Module
public class NetModule {
#Provides
#Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
#Provides
#Singleton
MainInterceptor provideMyApiInterceptor() {
return MainInterceptor.get();
}
#Provides
#Singleton
OkHttpClient provideOkhttpClient() {
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(15, TimeUnit.SECONDS);
client.connectTimeout(20, TimeUnit.SECONDS);
return client.build();
}
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("first.url.com")
.client(okHttpClient)
.build();
}
}
NetComponent.java
#Singleton
#Component(modules = NetModule.class)
public interface NetComponent {
Retrofit getRetrofit();
MainInterceptor getInterceptor();
}
MainInterceptor.class
public class MainInterceptor implements Interceptor {
private static MainInterceptor sInterceptor;
private String mScheme;
private String mHost;
public static MainInterceptor get() {
if (sInterceptor == null) {
sInterceptor = new MainInterceptor();
}
return sInterceptor;
}
private MainInterceptor() {
}
public void setInterceptor(String url) {
HttpUrl httpUrl = HttpUrl.parse(url);
mScheme = httpUrl.scheme();
mHost = httpUrl.host();
}
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
if (mScheme != null && mHost != null) {
HttpUrl newUrl = original.url().newBuilder()
.scheme(mScheme)
.host(mHost)
.build();
original = original.newBuilder()
.url(newUrl)
.build();
}
return chain.proceed(original);
}
}
And this is the code to initialize the component in some class.
NetComponent component= DaggerNetComponent.create();
DataService service = component.getRetrofit().create(DataService.class);
MainInterceptor interceptor = component.getInterceptor();
interceptor.setInterceptor("second.url.com");
service.getSomeData();
After that, the URL is still "first.url.com"
The simple way is to use #Named:
#Provides
#Singleton
#Named("retrofit_1")
Retrofit provideRetrofit1(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
...
.baseUrl("url_1.com")
...;}
#Provides
#Singleton
#Named("retrofit_2")
Retrofit provideRetrofit2(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
...
.baseUrl("url_2.com")
...;}
then use the same #Named:
#Provides
IApi1 provideApi_1(#Named("retrofit_1") RestAdapter adapter){....}
#Provides
IApi2 provideApi_2(#Named("retrofit_2") RestAdapter adapter){....}
When you provide a Retrofit, you already set baseUrl "first.url.com"
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("first.url.com")
.client(okHttpClient) //<------ It not OkHttp with your MainInterCeptor
.build();
And fun fact is, your MainInterceptor which is set-up with new url is unrelated to your Retrofit, because Retrofit was built with OkHttp
#Provides
#Singleton
OkHttpClient provideOkhttpClient() {
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(15, TimeUnit.SECONDS);
client.connectTimeout(20, TimeUnit.SECONDS);
return client.build();
}
If you want to dynamically change your BASE_URL, there are many ways for you to do that.
public class ApiConstant {
public static String BASE_URL = "yourUrl";
private ApiConstant() {
}
}
Create a #Scope for Retrofit because it can be changed
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface PerActivity {
}
Then use it when building Retrofit
#Provides
#PerActivity
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("first.url.com")
.client(ApiConstant.BASE_URL)
.build();
}
Then change the BASE_URL when init DaggerActivityComponent. (Or create a new #Scope for new url)
Read this documentation for more detail
Hope this help!
Add Interceptor in your AppModule
#Provides
#Singleton
OkHttpClient provideOkHttpClient(Cache cache, MainInterceptor interceptor) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.cache(cache)
.build();
return okHttpClient;
}
and then in your activity or presenter set url before calling retrofit service
mInterceptor.setInterceptor(urlname);
mRetrofitService.call();
If you already have a base URL set but you want to override it for just one API call and not all of them, it can be done pretty easily.
#PUT("https://my-api.com/user")
fun cancelOrder(#Path("user") user: String): Single<MyResponse>
Works the same way for POST and GET as well.

Jersey test - ExceptionMapper - UniformInterfaceException when HTTP status is not 200

I have to test Jersey 1.19 with jersey-test-framework-grizzly2. There is configuration class with registered REST endpoint and exception mapper class:
public class ConfiguredMyServiceTest extends JerseyTest {
#Override
protected int getPort(int defaultPort) {
return 8080;
}
public static class AppConfig extends DefaultResourceConfig {
public AppConfig() {
getSingletons().add(new ExceptionMapperProvider());
getSingletons().add(new MyService());
}
}
#Override
public WebAppDescriptor configure() {
return new WebAppDescriptor.Builder()
.initParam(WebComponent.RESOURCE_CONFIG_CLASS,
AppConfig.class.getName())
.build();
}
}
When I execute/test REST endpoint which returns HTTP status 200 it works well.
If exception is thrown, exception mapper handles it well and forms return object javax.ws.rs.core.Response with error code:
#Provider
#Singleton
public class ExceptionMapperProvider implements ExceptionMapper<Exception>{
#Override
public Response toResponse(final Exception exception){
return Response.status(HttpStatusCodes.STATUS_CODE_SERVER_ERROR).entity(new BasicResponse(InternalStatus.UNHANDLED_EXCEPTION, exception.getMessage())).type(MediaType.APPLICATION_JSON).build();
}
}
However, I get
com.sun.jersey.api.client.UniformInterfaceException: POST http://localhost:8080/v1/my-service/ returned a response status of 401 Unauthorized
when I try to assert Response in my JUnit tests. How to get well formed response instead of UniformInterfaceException?
Changed expected class type to com.sun.jersey.api.client.ClientResponse
protected ClientResponse executeGet(String path){
WebResource resource = resource().path(path);
Builder builder = resource.header("Content-Type", "application/json;charset=UTF-8");
return builder.get(ClientResponse.class);
}
Now it is able to handle various HTTP statuses and parse underlying response:
ClientResponse clientResponse = executeGet("/info");
if (clientResponse.getStatus() == 200)
CustomResponseType customResponse = clientResponse.getEntity(CustomResponseType.class);

Fatal exception when I run my application (retrofit)?

So I have an application which pulls information from an API using retrofit as the library. I thought I had it all working but whenever I run the application I get a null pointer exception and the app crashes and I'm unsure why:
The interface that builds retrofit:
public interface FriendsAPI {
static final String URL = "https://www.cs.kent.ac.uk/";
#GET("https://www.cs.kent.ac.uk/people/staff/iau/LocalUsers.php")
Call<User> getFriends();
class Factory {
private static FriendsAPI service;
public static FriendsAPI getInstance() {
if (service == null) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(URL)
.build();
return service;
} else {
return service;
}
}
}
}
Stores the JSON array into a list:
public class FriendsInfo {
#SerializedName("Users")
#Expose
private List<User> Users = new ArrayList<User>();
/**
*
* #return
* The Users
*/
public List<User> getUsers() {
return Users;
}
/**
*
* #param Users
* The Users
*/
public void setUsers(List<User> Users) {
this.Users = Users;
}
}
Finally where I'm calling it (and the code that triggers the fatal exception, although I don't know why):
public void populateFriends(){
FriendsAPI.Factory.getInstance().getFriends().enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
String tempLat = response.body().getLat();
String tempLon = response.body().getLon();
tLat = Double.parseDouble(tempLat);
tLon = Double.parseDouble(tempLon);
}
#Override
public void onFailure(Call<User> call, Throwable t) {
Log.e("Failed :(",t.getMessage());
}
});
}
I think I've included all the relevant code but if there is anything missing I can post it here. I also have a more complete version of the project on Github.
Thanks in advance guys.
You have not written anything to service inside getInstance(). Hence it is always null. You need to assign the retrofit.create() object to service.
public static FriendsAPI getInstance() {
if (service == null) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(URL)
.build();
service = retrofit.create(FriendsAPI.class); //Add this line
return service;
} else {
return service;
}
}
}
} else {
return service;
}
}
If service is null you return null, change it like this
class Factory {
private static MyApiEndpointInterface service;
if (service == null) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(URL)
.build();
service=retrofit.create(FriendsApi.class);
return service;
} else {
return service;
}
}
}

Categories