+ All Categories
Home > Software > Android Meetup Slovenija #3 - Testing with Robolectric by Ivan Kust

Android Meetup Slovenija #3 - Testing with Robolectric by Ivan Kust

Date post: 15-Apr-2017
Category:
Upload: infinum
View: 342 times
Download: 0 times
Share this document with a friend
39
Testing with Robolectric IVAN KUŠT, ANDROID TEAM LEADER
Transcript

Testing with Robolectric

IVAN KUŠT, ANDROID TEAM LEADER

ROBOLECTRIC

• http://robolectric.org/

• unit test framework

• tests run in JVM on your machine

CODE EXAMPLE

• https://github.com/infinum/Dagger-2-Example

01SETTING UP ROBOLECTRIC

ROBOLECTRIC SETUP

1. Add gradle dependencies

2. write test Application class

3. write test class

4. run tests

1. ADD DEPENDENCIES

• make sure “Unit tests” is selected as Test Artifact under

Build Variants

• add the robolectric dependency in your app build.gradle file:

// RobolectrictestCompile 'junit:junit:4.12'testCompile 'org.hamcrest:hamcrest-library:1.3'testCompile 'org.apache.maven:maven-ant-tasks:2.1.3' // fixes issue on linux/mactestCompile('org.robolectric:robolectric:3.0')

2. WRITE TEST APPLICATION CLASS

• all unit test code goes into test flavour

• Test{applicationName} • must extend your application class • runs instead of application class in tests • should inject test dependencies

3. WRITE TEST CLASS

• create new class in test flavor

• specify runner with @RunWith annotation

• add @Config annotation

• add test methods and annotate them with @Test

3.5 TEST CONFIGURATION

• constants property of @Config annotation is mandatory

• options (except constants) can be specified in a file instead: /test/resources/robolectric.properties

• for other options check:http://robolectric.org/configuring/

@RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21) public class DeckardActivityTest { @Test public void testSomething() throws Exception { assertTrue(Robolectric.setupActivity(DeckardActivity.class) != null); } }

4. RUN TESTS

• from Android studio

• ./gradlew clean test

TEST REPORT

• will be generated in:/app/build/reports/tests/{flavour}/index.html

• if tests faill, gradle will print out the location of the report in

console as well

LOGS

• to show Log.d() outputs add following in your @Before

method:ShadowLog.stream = System.out;

01SYNCHRONOUS EXECUTORS

(A)SYNCHRONOUS EXECUTORS

• unit tests run in a single thread

• waiting for background threads complicates tests

• solution: Synchronous executors (for Retrofit)

SETUP

• setting executor on Retrofit:

• specify executor for networking and callbacks

• for tests: both synchronous

return new RestAdapter.Builder() ... .setExecutors(new BackgroundExecutor(), new CallbackExecutor()) ... .build();

SYNCHRONOUS EXECUTOR?

• runs the queued runnable in the same thread

• simple as that:

public class SynchronousExecutor implements Executor { @Override public void execute(Runnable command) { command.run(); } }

01MOCK WEB SERVER

MOCK WEB SEVER

• part of OkHttp library by Square

• network responses must be mocked in unit tests • speed • consistency

MOCK WEB SERVER DEPENDENCIES

• add the MockWebServer dependency in your app

build.gradle file:

• MockWebServer depends on OkHttp library • make sure that versions match

testCompile ‘com.squareup.okhttp:mockwebserver:2.4.0'

USING MOCKWEBSERVER

1. Instantiate MockWebServer

2. call start() method

3. get local server url by calling url(“/“)

4. inject local server url into networking module

5. enqueue responses using enqueue() method

INSTANTIATING MOCK WEB SERVER

• prepare and inject MockWebServer in @Before method

(called before every @Test method)

• stop MockWebServer in @After method (called after every

@Test method)

INSTANTIATING MOCK WEB SERVER

• another option: • implement TestLifecycleApplication in Test application

class

• prepare and inject MockWebServer inbeforeTest(Method) method in Test application class

• stop MockWebServer in:afterTest(Method) in Test application class

ENQUEUEING RESPONSES

• enqueue(), MockResponse object

• https://github.com/square/okhttp/blob/master/

mockwebserver/src/main/java/com/squareup/okhttp/

mockwebserver/MockResponse.java

mockWebServer.enqueue( new MockResponse() .setResponseCode(200) .setBody(“Response body”) );

WHAT ABOUT LARGE RESPONSES?

1. Store response body content to a file

2. put the file inside /test/resources/ directory

3. read the contents of the file in test:

public static String readFromFile(String filename) { InputStream is = ResourceUtils.class.getClassLoader().getResourceAsStream(filename)); return convertStreamToString(is); }

CHECKING REQUESTS

• mockWebServer.getRequestCount()

• mockWebServer.takeRequest()

• mockWebServer.takeRequest(long, TimeUnit)

EXAMPLE

@Testpublic void nameOk() throws Exception { PokemonTestApp.getMockWebServer().enqueue( new MockResponse() .setResponseCode(200) .setBody(ResourceUtils.readFromFile("charizard.json")) ); String resourceUri = "api/v1/pokemon/6/"; Pokemon pokemon = new Pokemon(); pokemon.setResourceUri(resourceUri); Activity activity = buildActivity(pokemon); RecordedRequest request = takeLastRequest(); //Perform the assertions}

01SHADOW CLASSES

SHADOW CLASSES

• if your project structure prevents you from injecting mock

classes in unit tests

• any class can be swapped with a “shadow” class

SHADOWING A CLASS

1. Create a custom Robolectric runner

2. Declare shadowed classes in createClassLoaderConfig()

method

3. Implement shadow class

4. Add shadow classes to tests in @Config

5. Run tests with custom runner

CREATE A CUSTOM RUNNER

public class CustomRobolectricTestRunner extends RobolectricTestRunner { /** * Creates a runner to run {@code testClass}. Looks in your working directory for your * AndroidManifest.xml file * and res directory by default. Use the {@link Config} annotation to configure. * * @param testClass the test class to be run * @throws InitializationError if junit says so */ public CustomRobolectricTestRunner(Class<?> testClass) throws InitializationError { super(testClass); } /** * Declare custom classes to be shadowed when shadow exist. */ public InstrumentationConfiguration createClassLoaderConfig() { InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder(); builder.addInstrumentedClass(GoogleCloudMessaging.class.getName()); return builder.build(); } }

IMPLEMENT SHADOW CLASS

• create a new class with @Implements(OriginalClass.class)

annotation

• add @Implementation annotation to methods that “override”

behaviour from original class

• you can leave some methods unchanged

• if you wish to use an instance of OriginalClass in your

shadow class it must be annotated with @RealObject and

accessed through reflection

ADD SHADOWS TO TEST

• in order to make your shadows available in test add them to

@Config in your test: @Config(shadows = {ShadowClass.class})

EXAMPLE SHADOW CLASS

@Implements(GoogleCloudMessaging.class) public class ShadowGoogleCloudMessageing { @Implementation public void close() { Log.d("GoogleCloudMessageing", "close()"); } @Implementation public void send(String to, String msgId, Bundle data) throws IOException { Log.d("GoogleCloudMessageing", "send()"); } @Deprecated @Implementation public synchronized String register(String... senderIds) throws IOException { Log.d("GoogleCloudMessageing", "register()"); return "1234567890"; } @Implementation public String getMessageType(Intent intent) { Log.d("GoogleCloudMessageing", "getMessageType()"); return "gcm"; } }

01ASSERT J ANDROID

ASSERT J ANDROID

• https://github.com/square/assertj-android

• extension of AssertJ

• you can extend and set up your own assertions

EXAMPLE

//Check that name in details is displayed properly.assertThat(activity.findViewById(R.id.name).getVisibility()) .isEqualTo(View.VISIBLE); assertThat(((TextView) activity.findViewById(R.id.name)) .getText()).isEqualTo("Charizard");

SUMMARY

• use Robolectric for tests of a single Activity or Fragment

• use synchronous executors with Retrofit in tests

• use MockWebServer to easily mock out network responses

• use Shadows for classes that you can’t replace via

dependency injection

• AssertJ Android simplifies your assertions on Android stuff

CODE EXAMPLE

• https://github.com/infinum/Dagger-2-Example

Thank you!

Visit www.infinum.co or find us on social networks:

infinum.co infinumco infinumco infinum


Recommended