I am trying to write unit tests for repository while using MVVM pattern in android.
What i have is a repository which fetched data from the network using retrofit
public class ValidateCbuRepository {
private static ValidateCbuRepository single_instance = null;
private MutableLiveData<CBUValidationImageResponse> data = new MutableLiveData<>();
public static ValidateCbuRepository getInstance() {
if (single_instance == null)
single_instance = new ValidateCbuRepository();
return single_instance;
}
public MutableLiveData<CBUValidationImageResponse> processImage(String encodedString) {
JsonObject postParam = new JsonObject();
postParam.addProperty("image", encodedString);
Api service = RetrofitClientInstance.getRetrofitInstance().create(Api.class);
data.setValue(null);
HttpUrl httpUrl = HttpUrl.parse("some url");
Call<CBUValidationImageResponse> responseCall = service.getProcessedImage_cbu_validation(httpUrl.toString(),postParam);
responseCall.enqueue(new Callback<CBUValidationImageResponse>() {
#Override
public void onResponse(Call<CBUValidationImageResponse> call, Response<CBUValidationImageResponse> response) {
if(response.isSuccessful()) {
CBUValidationImageResponse res = response.body();
CBUValidationImageResponse cbuValidationImageResponse = res;
Log.i("CBU response ",""+cbuValidationImageResponse.toString());
cbuValidationImageResponse.setSuccess(true);
cbuValidationImageResponse.setShowProgres(false);
cbuValidationImageResponse.setError(false);
data.setValue(cbuValidationImageResponse);
}
}
#Override
public void onFailure(Call<CBUValidationImageResponse> call, Throwable t) {
CBUValidationImageResponse cbuValidationImageResponse = new CBUValidationImageResponse();
cbuValidationImageResponse.setError(true);
cbuValidationImageResponse.setShowProgres(false);
data.setValue(cbuValidationImageResponse);
t.printStackTrace();
}
});
return data;
}
}
The unit test part
#Mock
private Observer<CBUValidationImageResponse> observer;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testApiResponse_success() {
Api mockedApiInterface = Mockito.mock(Api.class);
Call<CBUValidationImageResponse> mockedCall = Mockito.mock(Call.class);
Mockito.when(mockedApiInterface.getProcessedImage_cbu_validation(any(),any())).thenReturn(mockedCall);
try {
Mockito.doAnswer(new Answer() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Callback<CBUValidationImageResponse> callback = invocation.getArgument(0);
CBUValidationImageResponse cbuValidationImageResponse = new CBUValidationImageResponse();
cbuValidationImageResponse.setCBU_code("some code");
cbuValidationImageResponse.setHeight(7);
cbuValidationImageResponse.setBreadth(7);
cbuValidationImageResponse.setLength(7);
callback.onResponse(mockedCall, Response.success(cbuValidationImageResponse));
// or callback.onResponse(mockedCall, Response.error(404. ...);
// or callback.onFailure(mockedCall, new IOException());
return null;
}
}).when(mockedCall).enqueue(any(Callback.class));
ValidateCbuRepository validateCbuRepository = new ValidateCbuRepository();
String encodedString= "";
validateCbuRepository.processImage(encodedString).observeForever(observer);
Getting a null pointer exception at validateCbuRepository.processImage(encodedString).observeForever(observer). Next step is to verify the observer.
I expect the test to pass. What am i doing wrong here?. I did something similar foe view model and the test passes with 100% code coverage.
The retrofit call is asynchronous. Is that the reason why it fails?
Edit : It seems livedata is null while testing causing NPE.
Related
here is my code, AgentRest is not mocked in A class
class A {
public void t() throws IOException {
AgentRest agentRest = new AgentRest("127.0.0.1", 8888);
HttpResponse<TaskStatusResponse> a = agentRest.dataBackup(null); // not mock
}
}
#Slf4j
#PrepareForTest({A.class, SftpClientTest.class,AgentRest.class })
#RunWith(PowerMockRunner.class)
class SftpClientTest {
#Test
void getHome() throws Exception {
HttpResponse<TaskStatusResponse> httpResponse =
HttpResponse.<TaskStatusResponse>builder().code(0).body(TaskStatusResponse.builder().status("").build()).build();
AgentRest agentRest = PowerMockito.mock(AgentRest.class);
PowerMockito.whenNew(AgentRest.class).withAnyArguments().thenReturn(agentRest);
PowerMockito.when(agentRest.dataBackup(ArgumentMatchers.any())).thenReturn(httpResponse);
new A().t();
log.info("");
}
}
i have try a lot but still failed, PowerMockito.whenNew seams not working, and i have added all class to PrepareForTest
I have found the probelm is junit5 is not working with powermock, solution link: https://rieckpil.de/mock-java-constructors-and-their-object-creation-with-mockito/
here is my new code:
class A {
public void t() throws IOException {
AgentRest agentRest = new AgentRest("127.0.0.1", 8888);
HttpResponse<TaskStatusResponse> a = agentRest.dataBackup(null);
}
}
#Slf4j
class SftpClientTest {
#Test
void getHome() throws Exception {
try (MockedConstruction<AgentRest> mocked = mockConstruction(AgentRest.class)) {
HttpResponse<TaskStatusResponse> httpResponse =
HttpResponse.<TaskStatusResponse>builder().code(0).body(TaskStatusResponse.builder().status("").build()).build();
// every object creation is returning a mock from now on
AgentRest agentRest = new AgentRest("sa", 22);
when(agentRest.dataBackup(ArgumentMatchers.any())).thenReturn(httpResponse);
new A().t();
}
}
}
I have an interface defined as follows:
public interface HttpClient {
public <T> UdemyResponse<T> get(Request request,
JSONUnmarshaler<T> unmarshaller, Gson gson)
throws UdemyException, IOException;
}
I have a class that implements the interface:
public class OkHttp implements HttpClient {
public OkHttpClient client;
final Logger logger = LoggerFactory.getLogger(getClass());
public OkHttp() {
this.client = new OkHttpClient();
}
#Override
public <T> UdemyResponse<T> get(Request request, JSONUnmarshaler<T> unmarshaller, Gson gson)
throws UdemyException, IOException {
int status_code = 0;
String next = null;
String rawJSON = null;
JsonElement jsonelement = null;
Boolean retry = true;
int attempts = 3;
while ((attempts >= 0) && (retry) && status_code != 200) {
try {
Response response = this.client.newCall(request).execute();
rawJSON = response.body().string();
jsonelement = gson.fromJson(rawJSON, JsonElement.class);
next = gson.fromJson(jsonelement.getAsJsonObject().get("next"), String.class);
status_code = response.code();
if (status_code == 401) {
try {
logger.warn("token expired");
TimeUnit.SECONDS.sleep(5);
retry = true;
continue;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if ((status_code / 100) == 5) {
logger.warn("gateway error");
retry = true;
continue;
}
} catch (IOException e) {
e.printStackTrace();
// this exception will be propagated to the main method and handled there to exit the program,
// this exception should end the program.
throw e;
}
attempts -= 1;
retry = false;
}
if (status_code != 200) {
throw new UdemyException();
}
return new UdemyResponse<T>(status_code, next, rawJSON,
unmarshaller.fromJSON(gson, jsonelement.getAsJsonObject()));
}
If I mock my interface I can write test cases for get() method but my get() method uses the this.client and I need to mock that object as well.
In this case, is it better to mock the OkHttp object rather than the interface?
If you are attempting to test get() then you should not mock that method, if you do, what is it that you are testing? You need to mock the other dependencies of get() to help you test it in isolation. In this case if this.client is a dependency of get(), this is what you need to mock.
Edited in response to question changes
This is terrible: (status_code / 100).
Test for the real status code there.
You should do the following:
Create a mock OkHttpClient.
Inject the mock into your test class using reflection.
test the get method.
You may want to change the mocking of the ok thing in the code below,
but you should be able to just use simple Mockito mocks for everything.
Here is some example code:
public class TestOkHttp
{
private static final String VALUE_JSON_STRING "some JSON string for your test";
private OkHttp classToTest;
#Mock
private ClassWithExecute mockClassWithExecute;
#Mock
private OkHttpClient mockOkHttpClient;
#Mock
private Response mockResponse;
#Mock
private ResponseBodyClass mockResponseBodyClass;
#Mock
private Request mockRequest;
private Gson testGson;
#Test
public void get_describeTheTest_expectedResults()
{
final JSONUnmarshaler<someclass> unmarshallerForThisTest = new JSONUnmarshaler<>()
// setup the mocking functionality for this test.
doReturn(desiredStatusCode).when(mockResponse).code();
classToTest.get()
}
#Before
public void preTestSetup()
{
MockitoAnnotations.initMocks(this);
classToTest = new OkHttp();
testGson = new Gson();
doReturn(mockResponse).when(mockClassWithExecute).execute();
doReturn(mockClassWithExecute).when(mockOkHttpClient).newCall(mockRequest);
doReturn(mockResponseBodyClass).when(mockResponse).body();
doReturn(VALUE_JSON_STRING).when(mockResponseBodyClass).string();
ReflectionTestUtils.setField(classToTest,
"client",
mockOkHttpClient);
}
}
I have the following tests. When I run them separately they pass. If I run all of them only the first passes.
Business Logic gets a JSON response from the APIServiceTask code. It creates an event to post using EventBus. I am writing tests to verify that EventBus is creating the correct calls.
The JSON Reponses class at the end is just the answers I am trying to post. If I run all of the tests, it seems like the loginFailureChangePasswordJSON is the one posted to the business logic.
public class LoginBusinessLogic {
private static LoginBusinessLogic instance = null;
APIServiceTask apiServiceTask;
public static LoginBusinessLogic getInstance(APIServiceTask apiServiceTask) {
if (instance == null) {
instance = new LoginBusinessLogic();
instance.apiServiceTask = apiServiceTask;
}
return instance;
}
protected void doLogin() {
EventBus.getDefault().register(this);
apiServiceTask.execute();
}
#Subscribe
public void onEvent(ServiceResultEvent event) {
switch (event.event) {
case failed:
handleLoginError(event.result);
break;
case cancelled:
EventBus.getDefault().postSticky(new LoginEvent(LoginEvent.TYPE_CANCELLE, event.result));
break;
case error:
if(event.originatorEvent != LoginEvent.TYPE_TOUCH_TOKEN_DELETE) {
EventBus.getDefault().postSticky(new LoginEvent(LoginEvent.TYPE_ERROR, event.result));
}
break;
default:
break;
}
EventBus.getDefault().unregister(this);
}
private void handleLoginError(String error) {
ErrorModel signInError = new Gson().fromJson(error, ErrorModel.class);
int statusCode = signInError.getMTBStatusCode();
String errMsg;
if (statusCode == 40022) {
errMsg = signInError.getUserMessage();
} else {
errMsg = signInError.getUserMessage().replace("*", "").replace("\"", "");
}
if (statusCode == 40001) {
EventBus.getDefault().postSticky(new LoginEvent(LoginEvent.TYPE_FAILED, statusCode, errMsg, false, false));
} else if (statusCode == 40108) {
EventBus.getDefault().postSticky(new LoginEvent(LoginEvent.TYPE_FAILED, statusCode, errMsg, true, false));
}
else if (statusCode == 40107) {
EventBus.getDefault().postSticky(new LoginEvent(LoginEvent.TYPE_FAILED, statusCode, errMsg, false, false));
} else if (statusCode == 40104) {
EventBus.getDefault().postSticky(new LoginEvent(LoginEvent.TYPE_FAILED, statusCode, errMsg, false, true));
} else {
EventBus.getDefault().postSticky(new LoginEvent(LoginEvent.TYPE_FAILED, statusCode, errMsg, false, false));
}
}
}
public class APIServiceTask {
public APIServiceTask(){
}
#SuppressWarnings("ConstantConditions")
public void execute() {
}
}
public class BusinessLogicTests {
#Mock
APIServiceTask service;
private LoginEvent loginEvent;
private LoginBusinessLogic loginBusinessLogic;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
loginBusinessLogic = LoginBusinessLogic.getInstance(service);
EventBus.getDefault().register(this);
}
#After
public void tearDown(){
EventBus.getDefault().unregister(this);
}
#Subscribe
public void onEvent(LoginEvent event){
loginEvent = event;
}
#Test
public void badUsernamePasscode(){
doAnswer(JSONResponses.loginInvalidUsernamePasscodeJSON())
.when(service).execute();
loginBusinessLogic.doLogin();
Assert.assertEquals(40108, loginEvent.mtbStstusCode);
}
#Test
public void accountBlocked(){
doAnswer(JSONResponses.loginAccountBlockedJSON())
.when(service).execute();
loginBusinessLogic.doLogin();
Assert.assertEquals(40104, loginEvent.mtbStstusCode);
}
#Test
public void temporaryPasscode(){
doAnswer(JSONResponses.loginTemporaryPasscodeJSON())
.when(service).execute();
loginBusinessLogic.doLogin();
Assert.assertEquals(40109, loginEvent.mtbStstusCode);
}
#Test
public void changedPasscode(){
doAnswer(JSONResponses.loginFailureChangePasscodeJSON())
.when(service).execute();
loginBusinessLogic.doLogin();
Assert.assertEquals(40107, loginEvent.mtbStstusCode);
}
}
public class JSONResponses {
public static Answer loginFailureChangePasscodeJSON(){
Answer answer = new Answer() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String result = "{\"MTBStatusCode\":40107, \"UserMessage\":\"Your passcode has changed since last login.\"}";
EventBus.getDefault().post(new ServiceResultEvent(ServiceResultEvent.EVENT_TYPE.failed, result, 0));
return null;
}
};
return answer;
}
public static Answer loginAccountBlockedJSON(){
Answer answer = new Answer() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String result = "{\"Version\":1,\"MTBStatusCode\":40104,\"HttpStatus\":401,\"UserMessage\":\"\\\"Your account is locked due to too many failed login attempts. <br><br>Reset Passcode >\\\"\",\"DeveloperMessage\":\"\\\"Account locked via multi-factor authentication.\\\"\"}";
EventBus.getDefault().post(new ServiceResultEvent(ServiceResultEvent.EVENT_TYPE.failed, result, 0));
return null;
}
};
return answer;
}
public static Answer loginInvalidUsernamePasscodeJSON(){
Answer answer = new Answer() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String result = "{\"Version\":1,\"MTBStatusCode\":40108,\"HttpStatus\":401,\"UserMessage\":\"\\\"User ID or Passcode doesn’t match. Try again.\\\"\",\"DeveloperMessage\":\"\\\"Voyager Error -1073739414 : User ID or Passcode doesn’t match. Try again.\\\"\"}";
EventBus.getDefault().post(new ServiceResultEvent(ServiceResultEvent.EVENT_TYPE.failed, result, 0));
return null;
}
};
return answer;
}
public static Answer loginTemporaryPasscodeJSON(){
Answer answer = new Answer() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String result = "{\"Version\":1,\"MTBStatusCode\":40107,\"HttpStatus\":401,\"UserMessage\":\"\\\"You have logged in with a temporary passcode. Log in to M&T Online Banking to create a new passcode.\\\"\",\"DeveloperMessage\":\"\\\"Password should be changed.\\\"\"}";
EventBus.getDefault().post(new ServiceResultEvent(ServiceResultEvent.EVENT_TYPE.failed, result, 0));
return null;
}
};
return answer;
}
}
For anyone interested, it seems the singleton still exists when the other tests run. 2 ways I found to fix it are nulling out the singleton in a separate method or moving the following statement outside of the if in LoginBusinessLogic.
instance.apiServiceTask = apiServiceTask;
This is a part of my class, I want to test:
public class PrefPanel extends Composite {
private static PrefPanelUiBinder uiBinder = GWT.create(PrefPanelUiBinder.class);
interface PrefPanelUiBinder extends UiBinder<Widget, PrefPanel> {}
public PrefPanel(GlobalParams globalParams) {
initWidget(uiBinder.createAndBindUi(this));
String url = URL.encode(globalParams.getBaseUrl() + "book.html");
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
try {
Request response = builder.sendRequest(jsonString, new RequestCallback() {
#Override
public void onError(Request request, Throwable exception) {
displayError("Error");
}
#Override
public void onResponseReceived(Request request, Response response) {
updateBookList(response.getText());
}
});
} catch (RequestException e) {
displayError("Error");
}
Here is a part of my test class:
#RunWith(GwtMockitoTestRunner.class)
public class PositionServiceTest {
#Mock RequestBuilder builder;
#Mock GlobalParams globalParams;
#Mock URL url;
private PrefPanel prefPanel;
#Before
public void setup() {
GwtMockito.useProviderForType(RequestBuilder.class, new FakeProvider() {
#Override
public Object getFake(Class aclass) {
return builder;
}
});
when(globalParams.getBaseUrl()).thenReturn("http://localhost/");
prefPanel = new PrefPanel(globalParams);
...
When I start to debug I get an error message:
- url cannot be empty
- java.lang.IllegalArgumentException
- at com.google.gwt.http.client.StringValidator.throwlfEmptyOrNull(StringValidator.java:52)
- ...
The error occurs on the line where I create the RequestBuilder (new RequestBuilder). I have no idea how to create a new instance of RequestBuilder. Could you give me a clue?
I have heard that gwtmockit can't handle constructors. Is there a way to avoid the new RequestBuilder? Do I have to use powermockito?
I have some old playframework 2.2 java webservice that interacts with akka, and now I should port them to playframework 2.3.
However, async has been deprecated and even after reading the doc about the async porting (http://www.playframework.com/documentation/2.3.x/JavaAsync) I wasn't able to understand how to apply it to my case (code below):
I must make the await for a timeout/akka server reply before starting the construction of my reply (ok()), otherwise I will block the thread.
I should make the actorselection async too.
I should make the akka server reply parsing/reply construction async too
I looked around and I wasn't able to find an example of such interactions, even in typesafe templates.
How could I do that?
/* playframework 2.2 code */
public class Resolve extends Controller {
private final static String RESOLVER_ACTOR = play.Play.application().configuration().getString("actor.resolve");
#CorsRest
#VerboseRest
#RequireAuthentication
#BodyParser.Of(BodyParser.Json.class)
public static Result getJsonTree() {
JsonNode json = request().body().asJson();
ProtoBufMessages.ResolveRequest msg;
ResolveRequestInput input;
try {
input = new ResolveRequestInput(json);
} catch (rest.exceptions.MalformedInputException mie) {
return badRequest(mie.getMessage());
}
msg = ((ProtoBufMessages.ResolveRequest)input.getMessage());
ActorSelection resolver = Akka.system().actorSelection(RESOLVER_ACTOR);
Timeout tim = new Timeout(Duration.create(4, "seconds"));
Future<Object> fut = Patterns.ask(resolver, input.getMessage(), tim);
return async (
F.Promise.wrap(fut).map(
new F.Function<Object, Result>() {
public Result apply(Object response) {
ProtoBufMessages.ResolveReply rsp = ((ProtoBufMessages.ResolveReply)response);
ResolveOutput output = new ResolveOutput(rsp);
return ok(output.getJsonReply());
}
}
)
);
}
}
I came out with the code below
public class Resolve extends Controller {
private final static String RESOLVER_ACTOR = play.Play.application().configuration().getString("actor.resolve");
private final static BrainProtoMessages.ResolveReply request_error = BrainProtoMessages.ResolveReply.newBuilder()
.setReturnCode(BResults.REQUEST_FAILED)
.build();
#CorsRest
#VerboseRest
#RequireAuthentication
#BodyParser.Of(BodyParser.Json.class)
public static Result resolve_map() {
final ResolveRequestInput input;
final F.Promise<ActorSelection> selected_target;
final F.Promise<Future<Object>> backend_request;
final F.Promise<BrainProtoMessages.ResolveReply> backend_reply;
final F.Promise<ObjectNode> decode_json;
final F.Promise<Result> ok_result;
final JsonNode json = request().body().asJson();
try {
input = new ResolveRequestInput(json);
} catch (rest.exceptions.MalformedInputException mie) {
return badRequest(mie.getMessage());
}
selected_target = F.Promise.promise(
new F.Function0<ActorSelection>() {
#Override
public ActorSelection apply() throws Throwable {
return Akka.system().actorSelection(RESOLVER_ACTOR);
}
}
);
backend_request =
selected_target.map(
new F.Function<ActorSelection, Future<Object>>() {
#Override
public Future<Object> apply(ActorSelection actorSelection) throws Throwable {
return Patterns.ask(actorSelection, input.getMessage(),new Timeout(Duration.create(4, "seconds")));
}
}
);
backend_reply = backend_request.map(
new F.Function<Future<Object>, BrainProtoMessages.ResolveReply>() {
#Override
public BrainProtoMessages.ResolveReply apply(Future<Object> akka_reply) throws Throwable {
try {
return (BrainProtoMessages.ResolveReply) Await.result(akka_reply, Duration.create(4, "seconds"));
}catch(Exception error)
{
return request_error;
}
}
}
);
decode_json = backend_reply.map(
new F.Function<BrainProtoMessages.ResolveReply, ObjectNode>() {
#Override
public ObjectNode apply(BrainProtoMessages.ResolveReply response) throws Throwable {
return new ResolveOutput(response).getJsonReply();
}
}
);
ok_result = decode_json.map(
new F.Function<ObjectNode, Result>() {
#Override
public Result apply(ObjectNode reply) {
return ok(reply);
}
}
);
try {
return ok_result.get(8000);
}catch(Exception error)
{
return internalServerError();
}
}
}