Android Unit Testing Implementation at Metova

 

Welcome back friends. Let’s continue our discussion on Android Unit Testing from last weeks session.

Android JUnit Testing

The Android testing framework is the first place to start when getting familiar with testing on the Android platform. The Android testing framework is built upon JUnit, which provides a number of useful assertion methods for verifying the state of the application at various points during a test.

assertEquals()

Tests two objects for equality using their equals() methods.

public void testFooBar() {  //create a mock of CalculationEngine  FooBar fb = new FooBar();  fb.setCalculationEngine( mockCalculationEngine );  int expected = 7;  int actual = fb.calculateSomething();  //throws an AssertionError and causes test to fail if condition is not met  assertEquals(expected, actual);}

assertTrue()/assertFalse()

Verifies a boolean condition.

public void testSomethingWithRobotium() {  //more on Robotium shortly  getActivity();  solo.clickOnButton("Login");  assertTrue(solo.waitForText("Invalid password"));}

Instrumentation

Many of our Android unit test classes are subclasses of InstrumentationTestCase, which gives access to an Instrumentation object.

The Instrumentation class provides a lot of low-level functions that we generally access through Robotium’s API (waiting for activities, getting String values from the project’s string resources, etc). However, it’s still worth understanding this class and what it provides. A couple of useful ones to know about:

waitForIdleSync()

Waits for the UI thread’s queue to become empty before proceeding. Generally one will want to use one of Robotium’s waitForXyz() methods, but there are times when this is more applicable.

getTargetContext()

Gets the Context for the application under test. This is different from Instrumentation#getContext(), which provides the test application’s Context.

runTestOnUiThread()

This method isn’t actually used to run an entire test on the UI thread, but rather it provides a way to run a portion of a test on the UI thread. This can be useful if you are directly manipulating Views in your test, since the Android framework will error if you modify views off of the main thread.

Mockito

Our preferred mocking library is Mockito. It provides a framework for creating mocks, defining behavior, and verifying interactions.

Mockito on Android uses the Dexmaker library to generate dynamic classes in the Dalvik format. You can add these to your project with:

androidTestCompile 'org.mockito:mockito-core:1.10.19'androidTestCompile 'com.google.dexmaker:dexmaker:1.1'androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'

Mockito provides a lot of static methods on the Mockito and Matchers classes. Using static imports can greatly increase the readability of your tests!

Creating mocks

Mocks are created via the Mockito.mock() method:

public void testFooBar() {  //using the Mockito.mock() method via static import  CalculationEngine mockCalculationEngine = mock( CalculationEngine.class );  FooBar fb = new FooBar();  fb.setCalculationEngine( mockCalculationEngine );  int expected = 7;  int actual = fb.calculateSomething();  //throws an AssertionError and causes test to fail if condition is not met  assertEquals(expected, actual);}

The instance of CalculationEngine is now a blank mock that returns default values for any unstubbed method invocation.

Establishing behavior

Behavior on a mock is most commonly established with the when(mock).thenReturn()-style of calls.

The when() method allows the developer to specify which method call on the mock object should be stubbed. E.g.,

//partial example of stubbing behavior on a mock objectwhen(mockCalculationEngine.calculateMagicNumber());

where calculateMagicNumber() is a method on the CalculationEngine object.

The above example provides an OngoingStubbing object, which isn’t very useful in a standalone sense. What’s needed is to define behavior for what should happen when the method in question is called.

thenReturn()

This is the most common method used to define behavior on a mock. It does just what it sounds like: for any subsequent call to this particular mock’s method, the specified value will be returned.

when(mockCalculationEngine.calculateMagicNumber()).thenReturn(7);

thenThrow()

This variation is particularly handy when testing error conditions. An invocation of the stubbed method on the mock object will result in a Throwable being raised.

when(mockHttpClient.connect()).thenThrow(new NetworkUnavailableException());

thenAnswer()

This is an advanced method that is fairly rare in our test code, but it can be powerful. It gives you access to the actual parameters of the method and allows you to specify logic that should occur when the stubbed method is invoked.

when(mockCalculationEngine.calculateMagicNumber(anyInt())).thenAnswer(new Answer<Integer>(){  public Integer answer(InvocationOnMock invocation) throws Throwable {    magicExpended = true;    return (Integer) invocation.getArguments()[0] * 27;  }});

doThrow()/doAnswer() variations

Do to the generic type parameters of OngoingStubbing, the previous methods require methods that have a return value. They are the preferred way of stubbing method behavior, as they are typesafe and more readable.

However, scenarios exist where a developer will want to stub behavior for a void method. The doAnswer/doThrow family of methods can be used in those cases.

doThrow(new RuntimeException()).when(mockHttpClient.clearRequestQueue());

Argument matching

If you use a matcher for one argument in a method call, you must use a matcher for ALL of them.

Many methods that a developer will want to stub have arguments. Specifying behavior on these methods means the mocking framework will try to match the arguments as specified by the stubbing definition. It’s possible to use literal arguments or argument Matchers to achieve this:

//asking for one piece of toast works...when(mockToaster.makeToast("1")).thenReturn( new Toast[]{new Toast()} );//but asking for two always burns the second piecewhen(mockToaster.makeToast("2"))  .thenReturn( new Toast[]{new Toast(), new BurntToast()} );//any call to update the status succeedswhen(mockWebClient.updateStatus( any(StatusParams.class), any(String.class) ))  .thenReturn( new HttpResponse(200) );//calls to update the status with the specific message have different behavior//we can't mix literals with argument matchers, so we must use Mockito.eq()!when(mockWebClient.updateStatus( any(StatusParams.class), eq("On a client call") ))  .thenReturn( new HttpResponse(401) );  

Overspecification and Test Fragility

It’s a good idea to avoid getting too specific with argument matching. If anyInt() will work, then use that instead of eq(“1”). Creating test conditions that are too specific will lead to brittle tests that break easily

Verification

Frequently, the best course of action in a test is to make assertions based on expected side effects of your test. E.g., when this error is produced, one should see this specific error dialog. Sometimes, however, the only reasonable course is to use the mock object itself to verify that a particular call was made, a particular argument was passed, etc. In Mockito parlance, this is known as “verification.

List mockedList = mock(List.class);mockedList.add("one");mockedList.clear();verify(mockedList).add("one");verify(mockedList).clear();

Timeouts

It’s important to understand that the thread upon which tests are executed is NOT the main Android thread (aka the “UI thread”). Many times an Android action will be invoked on an AsyncTask (another thread). Therefore, be careful about making calls to verify() without taking this into account, since you may end up with a race condition between your test thread (doing the verify()) and the UI thread or AsyncTask to actually do the work.

This is easily addressed by doing verification with a timeout:

solo.clickOnButton("Login");/* the call to login() occurs on a background thread; use the   Mockito.timeout() method */verify( mockWebClient.login(any(Credentials.class)), timeout(500) );

That’s it!

Thanks for coding with me. Next week we will close out with Part 3 of this series.