Android Local Tests vs Instrumented Test - android

I'm trying to check if my API is available within a unit test, to be sure that it responds with 200.
Now, my problem is that I'm not really sure when to use Local Test and when I have to use Android Instrumentation Tests. I know that I have to use Instrumented Tests for UI testing but how to test the endpoint?
I use Retrofit2 for communication. And tried to test Endpoint in 2 ways with Local Tests.
Example 1 (synchronous, does not work)
public class EndpointTest {
EndpointApi api;
SimpleInjection simpleInjection;
#Before
public void setUp() {
simpleInjection = new SimpleInjection();
api = simpleInjection.getEndpointApi();
}
#Test
public void endpoint_1_isAvailable() {
Call<ApiResponse> rootCall = api.getRoot();
try {
int reponseCode = rootCall.execute().code();
Assert.assertEquals(200, reponseCode);
} catch (IOException e) {
Assert.fail();
}
}
}
Example 2 (asynchronous, does work)
public class EndpointTest {
EndpointApi api;
SimpleInjection simpleInjection;
#Before
public void setUp() {
simpleInjection = new SimpleInjection();
api = simpleInjection.getEndpointApi();
}
#Test
public void endpoint_2_isAvailable() {
Call<ApiResponse> rootCall = api.getRoot();
rootCall.enqueue(new Callback<ApiResponse>() {
#Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
Assert.assertEquals(200, response.code());
}
#Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
Assert.fail();
}
});
}
}
Do I have to use Android Instrumentation Test for asynchronous mode?

The decision of whether to run your tests on a local JVM on your development machine or on an Android device/emulator is not based on whether your code is synchronous or not. Usually you would only want to run unit tests locally, as it's a lot faster and allows you to use TDD. Your tests do network requests, so they're not unit tests per say, since they have the dependency on the server - they are integration tests. It's preferable to run integration tests on an Android device to get better feedback.

Related

UIAutomator - Screenshot after a failure

I'm new in Android Testing. I'm using UIAutomator to make some Black-Box testing. I would like to take a screenshot each time a test fails. I tried to use the TestWatcher class by adding a Rule in my test class. But, it doesn't work. I guess we cannot use the #Rule annotation when using UIAutomator.
There are lots of related topics for Espresso but I didn't find anything for UIAutomator.
Does anyone have a method to solve my issue?
Thanks!
Yes, you can do it in this way:
#RunWith(AndroidJUnit4.class)
public class TestClass {
#Rule
public ScreenshotTakingRule rule = new ScreenshotTakingRule();
#Test
public void yourTest() {
}
}
and
/**
* Junit rule that takes a screenshot when a test fails.
*/
public class ScreenshotTakingRule extends TestWatcher {
#Override
protected void failed(Throwable e, Description description) {
Log.d(TAG, "taking screenshot..." + description.getMethodName());
takeScreenshot(description.getMethodName());
}
private void takeScreenshot(String screenShotName) {
ScreenCapture screenCapture = Screenshot.capture();
try {
screenCapture.setName(screenShotName);
Set<ScreenCaptureProcessor> processorSet = new HashSet<>();
processorSet.add(new SimpleNameScreenCaptureProcessor());
screenCapture.process(processorSet);
} catch (IOException ex) {
Log.d(TAG, "error while taking screenshot " + ex.getMessage());
}
}
}

Interprocess or adb communication with AndroidJUnit

I want to know, does any way exist to communicate with system during instrumentation test execution.
For example:
I have a phone with IR port on onboard & I can work with it through private SDK, also I can tune it with my application. In my Instrumentation test cases I want test app behavior based on external events which I want to configure before test separate test execution.
It's looks like
#Test
public void test() throws Exception {
setupExternalCondition(condition1_ON); // setup external transiver
assertNotNull(IR.read());
assertTrue(assertIR.write());
setupExternalCondition(condition1_OFF);
assertNotNull(IR.read());
assertFalse(IR.write());
}
It's very simple example but there is a lot of "conditions", and sdk updating frequencies to high. I can't do all of this verification manually, and can't ask "transiver&SDK team" make a mock states list for writing just a unit test for coverage. So I want somehow inject external component execution to TestRuner for receiving events(or testName before test case execution) on local machine(or CI machine) to setup external condition.
Simple solution(I think) to run a tcp server on appUnderTest and request external condition change - I am not sure does it possible, and not sure about stable connection(wifi), so may be it's possible to do over adb.
Any suggestions?
P.S: test device has root permissions.
So, find not bad but not ideal solution.
Still wait for better proposition, if not may be this answer will be helpful for someone;
For "building the bridge" between local machine and AndroidJUnitTest I add next class to tests:
class IPCServiceBridge extends BroadcastReceiver {
private static final String FILTER_ID = "IPC_SERVICE";
private static IPCServiceBridge sInstance;
private boolean mIsPermitted;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("ipc.service.action")) {
mIsPermitted = true;
}
}
public static IPCServiceBridge getInstance() {
if (sInstance == null) {
sInstance = new IPCServiceBridge();
IntentFilter filter = new IntentFilter();
filter.addAction("ipc.service.action");
Context context = InstrumentationRegistry.getContext();
context.registerReceiver(sInstance, filter);
}
return sInstance;
}
public void sendIpcCommand(String commandName) {
try {
int i = 30;
mIsPermitted = false;
while (i > 0) {
pub("request:" + commandName);
Thread.sleep(1000);
if (mIsPermitted) {
break;
}
i--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!mIsPermitted) {
throw new RuntimeException("IPC service does not respond");
}
}
private static void pub(String msg) {
Log.e(FILTER_ID, msg);
}
}
Than I start adb logcat -s "filter_name", parse and check which condition should be applied for InsttUnit test. When conditions is ready i send back broadcast receiver with required action.
#Test
public void test2() throws Exception {
IPCServiceBridge.getInstance().sendIpcCommand("CONDITION#123");
}
Work good, but I'm not sure that it will be super stable.

Error utilizing RealmDB while unit testing

I am writing a test for a ViewModel. The function in the ViewModel is this:
public void discoverMovies(boolean showLoading) {
// reset the states to initial states
moviesLoading.set(showLoading);
errorViewShowing.set(false);
emptyViewShowing.set(false);
mMoviesRepository.getPopularMovies(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribeWith(new DisposableObserver<List<Movie>>() {
#Override
public void onNext(List<Movie> value) {
// show or hide empty view
boolean isEmpty = value == null || value.isEmpty();
if (!isEmpty) {
saveResponse(value);
movies.clear();
movies.addAll(value);
}
emptyViewShowing.set(isEmpty);
}
#Override
public void onError(Throwable throwable) {
errorViewShowing.set(true);
moviesLoading.set(false);
emptyViewShowing.set(false);
errorString.set(getErrorMessage(throwable));
}
#Override
public void onComplete() {
moviesLoading.set(false);
errorViewShowing.set(false);
}
});
}
private void saveResponse(final MovieResponse mainResponse) {
Realm.getDefaultInstance().executeTransaction(new Realm.Transaction() {
#Override public void execute(Realm realm) {
RealmMovie realmMovie = realm.createObject(RealmMovie.class);
realmMovie.setId(1);
realmMovie.setMarvelResponse(new Gson().toJson(mainResponse));
}
});
}
And I test the function above in my test class like this:
Note: Everything works without the Realm aspect. I've confirmed that.
#Test
public void getPopularMoviesWithoutError() {
// given the following movies
when(mMoviesRepository.getPopularMovies(PAGE)).thenReturn(Observable.just(MOVIES));
// discover popular movies
mMoviesViewModel.discoverMovies(true);
// verify that the repository is called
verify(mMoviesRepository).getPopularMovies(PAGE);
// test that the loading indicator is hidden
assertFalse(mMoviesViewModel.moviesLoading.get());
// check that the empty view is hidden
assertFalse(mMoviesViewModel.emptyViewShowing.get());
// check that the error view is hidden
assertFalse(mMoviesViewModel.errorViewShowing.get());
assertTrue(mMoviesViewModel.movies.size() == MOVIES.size());
}
And it keeps on giving me java.lang.IllegalStateException: CallRealm.init(Context)before calling this method. How can I initialize Realm
to be available
I think the error message you are getting is quite clear about what is causing the problem. You are not calling Realm.init.
There are several ways of doing this. The simplest is the #Before and #After annotations on the test suite. You could also use a TestRule
Unfortunately, Realm.init requires a Context. To get that context, you are going to have to be in some environment that has one. That means that you will either have to run your tests on a device, as Instrumentation tests or, as #David Rawson suggests, use Robolectric.

How to unit test Retrofit api calls?

I am trying to integrate Unit test cases for every chunk of code possible.
But I am facing issues while adding test cases for api calls that are made through retrofit.
The JUnit compiler never executes the code in the CallBack functions.
There is another option of making all the api calls Synchronous for testing purpose, but that's not possible for every case in my app.
How can I sort this out? I have to add test cases in the api calls by any means.
If you use .execute() instead of .enqueue() it makes execution synchron, thus the tests can ran properly without the need of importing 3 different libraries and adding any code or modify the build variants.
Like:
public class LoginAPITest {
#Test
public void login_Success() {
APIEndpoints apiEndpoints = RetrofitHelper.getTesterInstance().create(APIEndpoints.class);
Call<AuthResponse> call = apiEndpoints.postLogin();
try {
//Magic is here at .execute() instead of .enqueue()
Response<AuthResponse> response = call.execute();
AuthResponse authResponse = response.body();
assertTrue(response.isSuccessful() && authResponse.getBearer().startsWith("TestBearer"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
I test my Retrofit callbacks using Mockito, Robolectric and Hamcrest libraries.
First of all, set up lib stack in your module's build.gradle:
dependencies {
testCompile 'org.robolectric:robolectric:3.0'
testCompile "org.mockito:mockito-core:1.10.19"
androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
}
In jour project's global build.gradle add following line to buildscript dependencies:
classpath 'org.robolectric:robolectric-gradle-plugin:1.0.1'
Then enter "Build Variants" menu in Android Studio (to quickly find it, hit Ctrl+Shift+A and search for it), and switch "Test Artifact" option to "Unit Tests". Android studio will switch your test folder to "com.your.package (test)" (instead of androidTest).
Ok. Set-up is done, time to write some tests!
Let's say you've got some retrofit api calls to retrieve a list of objects that need to be put into some adapter for a RecyclerView etc. We would like to test whether adapter gets filled with proper items on successful call.
To do this, we'll need to switch your Retrofit interface implementation, that you use to make calls with a mock, and do some fake responses taking advantage of Mockito ArgumentCaptor class.
#Config(constants = BuildConfig.class, sdk = 21,
manifest = "app/src/main/AndroidManifest.xml")
#RunWith(RobolectricGradleTestRunner.class)
public class RetrofitCallTest {
private MainActivity mainActivity;
#Mock
private RetrofitApi mockRetrofitApiImpl;
#Captor
private ArgumentCaptor<Callback<List<YourObject>>> callbackArgumentCaptor;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);
mainActivity = controller.get();
// Then we need to swap the retrofit api impl. with a mock one
// I usually store my Retrofit api impl as a static singleton in class RestClient, hence:
RestClient.setApi(mockRetrofitApiImpl);
controller.create();
}
#Test
public void shouldFillAdapter() throws Exception {
Mockito.verify(mockRetrofitApiImpl)
.getYourObject(callbackArgumentCaptor.capture());
int objectsQuantity = 10;
List<YourObject> list = new ArrayList<YourObject>();
for(int i = 0; i < objectsQuantity; ++i) {
list.add(new YourObject());
}
callbackArgumentCaptor.getValue().success(list, null);
YourAdapter yourAdapter = mainActivity.getAdapter(); // Obtain adapter
// Simple test check if adapter has as many items as put into response
assertThat(yourAdapter.getItemCount(), equalTo(objectsQuantity));
}
}
Proceed with the test by right clicking the test class and hitting run.
And that's it. I strongly suggest using Robolectric (with robolectric gradle plugin) and Mockito, these libs make testing android apps whole lotta easier.
I've learned this method from the following blog post. Also, refer to this answer.
Update: If you're using Retrofit with RxJava, check out my other answer on that too.
The JUnit framework never executes the code in the CallBack functions because the main thread of execution terminates before the response is retrieved. You can use CountDownLatch as shown below:
#Test
public void testApiResponse() {
CountDownLatch latch = new CountDownLatch(1);
mApiHelper.loadDataFromBackend(new Callback() {
#Override
public void onResponse(Call call, Response response) {
System.out.println("Success");
latch.countDown();
}
#Override
public void onFailure(Call call, Throwable t) {
System.out.println("Failure");
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
This test sample may be helpful too.
My advice isn't to perform testing for the API responses in the android app. There are many external tools for this.
Junit will not wait for async tasks to complete. You can use CountDownLatch (elegant solution which does NOT require an external library) to block the thread, until you receive response from server or timeout.
You can use CountDownLatch.
The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately.
//Step 1: Do your background job
latch.countDown(); //Step 2 : On completion ; notify the count down latch that your async task is done
latch.await(); // Step 3: keep waiting
OR you can specify a timeout in your await call
try {
latch.await(2000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
Sample Test Case
void testBackgroundJob() {
Latch latch = new CountDownLatch(1);
//Do your async job
Service.doSomething(new Callback() {
#Override
public void onResponse(){
ACTUAL_RESULT = SUCCESS;
latch.countDown(); // notify the count down latch
// assertEquals(..
}
});
//Wait for api response async
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
assertEquals(expectedResult, ACTUAL_RESULT);
}
if already encapsulation retrofit2.0 with rx with restful
open class BaseEntity<E> : Serializable {
/*result code*/
var status: Int = 0
/**data */
var content: E? = null
}
and server api request like
#GET(api/url)
fun getData():Observable<BaseEntity<Bean>>
your service call back just one sync request Observable
val it = service.getData().blockingSingle()
assertTrue(it.status == SUCCESS_CODE)
As #Islam Salah said:
The JUnit framework never executes the code in the CallBack functions because the main thread of execution terminates before the response is retrieved.
You can use awaitility to solve the problem. Check out this answer on StackOverflow.

Mock Retrofit Observable<T> response in Android Unit tests

I have an API interface and I'm testing a View that involves network calls.
#Config(emulateSdk = 18)
public class SampleViewTest extends RobolectricTestBase {
ServiceApi apiMock;
#Inject
SampleView fixture;
#Override
public void setUp() {
super.setUp(); //injection is performed in super
apiMock = mock(ServiceApi.class);
fixture = new SampleView(activity);
fixture.setApi(apiMock);
}
#Test
public void testSampleViewCallback() {
when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());
AtomicReference<Object> testResult = new AtomicReference<>();
fixture.updateView(new Callback() {
#Override
public void onSuccess(Object result) {
testResult.set(result);
}
#Override
public void onError(Throwable error) {
throw new RuntimeException(error);
}
});
verify(apiMock, times(1)).requestA();
verify(apiMock, times(1)).requestB();
assertNotNull(testResult.get());
}
}
For some reason apiMock methods are never called and verification always fails.
In my view I'm calling my api like this
apiV2.requestA()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer());
What am I missing here?
Update #1:
After some investigation it appears that when in my implementation (sample above) I observeOn(AndroidSchedulers.mainThread()) subscriber is not called. Still do not know why.
Update #2:
When subscribing just like that apiV2.requestA().subscribe(new Observer()); everything works just fine - mock api is called and test passes.
Advancing ShadowLooper.idleMainLooper(5000) did nothing. Even grabbed looper from handler in HandlerThreadScheduler and advanced it. Same result.
Update #3:
Adding actual code where API is used.
public void updateView(final Callback) {
Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
new Func2<ResponseA, ResponseB, Object>() {
#Override
public Object call(ResponseA responseA, ResponseB responseB) {
return mergeBothResponses(responseA, responseB);
}
}
).subscribe(new EndlessObserver<Object>() {
#Override
public void onError(Throwable e) {
Log.e(e);
listener.onError(e);
}
#Override
public void onNext(Object config) {
Log.d("Configuration updated [%s]", config.toString());
listener.onSuccess(config);
}
});
}
protected <T> Observable<T> wrapObservable(Observable<T> observable) {
return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
I'm still wrapping my head around how to properly use rxjava myself but I would try to modify your code so that you only observeOn(mainThread) on the final zipped Observable instead of doing it on both of the original request response's Observable. I would then verify if this affect the fact that you have to advance both Loopers or not.
To simply your tests and remove the need for Looper idling I would take the threading out of the equation since you don't need background processing when running tests. You can do that by having your Schedulers injected instead of creating them statically. When running your production code you'd have the AndroidSchedulers.mainThread and Schedulers.io injected and when running tests code you would inject Schedulers.immediate where applicable.
#Inject
#UIScheduler /* Inject Schedulers.immediate for tests and AndroidSchedulers.mainThread for production code */
private Scheduler mainThreadSched;
#Inject
#IOScheduler /* Inject Scheduler.immediate for tests and Schedulers.io for production code */
private Scheduler ioSched;
public void updateView(final Callback) {
Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
new Func2<ResponseA, ResponseB, Object>() {
#Override
public Object call(ResponseA responseA, ResponseB responseB) {
return mergeBothResponses(responseA, responseB);
}
}
).observeOn(mainThreadSched)
.subscribe(new EndlessObserver<Object>() {
#Override
public void onError(Throwable e) {
Log.e(e);
listener.onError(e);
}
#Override
public void onNext(Object config) {
Log.d("Configuration updated [%s]", config.toString());
listener.onSuccess(config);
}
});
}
protected <T> Observable<T> wrapObservable(Observable<T> observable) {
return observable.subscribeOn(ioSched);
}
what version of rxjava are you using? I know there was some changes in the 0.18.* version regarding the ExecutorScheduler. I had a similar issue as you when using 0.18.3 where I wouldn't get the onComplete message because my subscription would be unsubscribe ahead of time. The only reason I'm mentioning this to you is that a fix in 0.19.0 fixed my issue.
Unfortunately I can't really explain the details of what was fixed, it's beyond my understanding at this point but if it turns out to be the same cause maybe someone with more understand could explain. Here's the link of what I'm talking about https://github.com/Netflix/RxJava/issues/1219.
This isn't much of an answer but more a heads up in case it could help you.
As #champ016 stated there were issues with RxJava versions that are lower than 0.19.0.
When using 0.19.0 the following approach works. Although still don't quite get why I have to advance BOTH loopers.
#Test
public void testSampleViewCallback() {
when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());
AtomicReference<Object> testResult = new AtomicReference<>();
fixture.updateView(new Callback() {
#Override
public void onSuccess(Object result) {
testResult.set(result);
}
#Override
public void onError(Throwable error) {
throw new RuntimeException(error);
}
});
ShadowLooper.idleMainLooper(5000);
Robolectric.shadowOf(
Reflection.field("handler")
.ofType(Handler.class)
.in(AndroidSchedulers.mainThread())
.get().getLooper())
.idle(5000);
verify(apiMock, times(1)).requestA();
verify(apiMock, times(1)).requestB();
assertNotNull(testResult.get());
}

Categories

Resources