Efficient Android Testing Using Raccoon And Espresso

Do you feel discouraged when it comes to testing your Android Apps if the code has a server dependency?

Have you leveraged DI, Repository Pattern, MVI, MVP or MVVM pattern and using fakes or mocks to make your code highly testable but still feel something is incomplete?

Do you have a legacy code base and wonder if there could be a way to efficiently test it without having to do even a slight refactoring?

Well, you are at the right place.

Why Raccoon ?

There can be a solution like WireMock for the above problem. But what if you want to customize the response according to the request. I think you are getting my point… what if you can mock the complete states of your app. That is what a raccoon is all about. With Raccoon, you can write custom logic to the response that you need to provide according to the request.

Dive In

Raccoon is build on top of annotations and Runtime Reflection. Therefore, you will need to follow a specific pattern to use the library. Below are the core components of the library:

Controllers

Controller is a place where you define the endpoints. You will need to mention the @ControllerModule and extend RaccoonController.class to notify the Raccoon Core Modules that it is a controller class.

Here is a sample of how it looks.

@ControllerModule
class MockController : RaccoonController() {
override fun setup() {
// Do the DI related stuff
}

@RaccoonEndpoint(
endpoint = "${BASE_URL}todos/1",
responseTime = 100,
RaccoonRequestType.GET)
fun fetchToDoList(@Params headers: Parameters): RaccoonResponse
{
return Response(
id = 10,
userId = 1231,
completed = true,
title = "mock response"
).buildRaccoonResponse(statusCode = 200)
}
override fun tearDown() {
// clean up memory.
}
}
  • @ControllerModule : Used to specify if the class is a controller.
  • setup() : This function is the first function that is called before calling the endpoint function. You can write all the DI related code here.
  • tearDown() : This function is called after the execution of the endpoint function. You can write the clear-memory code in here.
  • @RaccoonEndpoint : This annotation is used to specify if the function is an endpoint. This annotation has basically 3 parameters going in:
  1. endpoint : The Endpoint of the URL that we the function to respond.
  2. responseTime : This is used to specify the response delay of an endpoint.
  3. requestType : This is for mentioning the request types for

The Endpoint function takes in maximum 2 arguments:

  1. Parameters: This is used to retrieve the headers and queryParams

from the request. This is how it looks

Parameters(    
// Headers passed in the request.
val headers: List<Pair<String, String>> = arrayListOf(),

// Query params passed in the request.
val queryParameters: Map<String, String> = mapOf()
)

2 . Request Body :

The request body should be a Moshi/Gson compactable class. You can see the implementation example of the same here.

The order of these parameters are not mandatory, Raccoon compiler automatically finds and injects the objects into the method signature.

The Response of the function should be of the type RaccoonResponse.

You can also convert any Gson/Moshi supported objects to the raccoons response with buildRaccoonResponse().

Service

A Service is a place where we mention the Controller classes. This is the entry point of the controller-service graph creation.

@RaccoonService
class MockService : RaccoonServiceImpl() {
@RaccoonController
fun providesGsonMockController() = MockController::class

}
  • @RaccoonService : Specifies if the class is a service or not.
  • Extend RaccoonServiceImpl class.
  • use @RaccoonController to specify the controller class in the service.

Now you are done with 90% of the configurations. Rest of the implementation are in Android Test files:

This is a sample implementation of the android test file:

@RunWith(AndroidJUnit4::class)
class ParsingTest {
@Before
fun setup() {
RaccoonStub.setUp(
RaccoonConfig.Builder()
.addService(MockService::class)
.build()
)
}
@get:Rule
val rule = ActivityTestRule(
MainActivity::class.java,
false, false
)
@Test
fun useAppContext() {
rule.launchActivity(null)
Thread.sleep(5000)
// Context of the app under test.
assert(true)
rule.activity.finish()
}

@After
fun tearDown() {
RaccoonStub.tearDown()
}
}

We will need to setup the Raccoon configuration in the start of the test. This will make initialize service-controller graph. All the endpoint mocking logics will be initiated under the hood.

Developers now a days generally use Gson/Moshi for the network related functionalities. Raccoon is made in such a way that, you can reuse all your request-response objects that you had already used in your project. For this, you need to specify the parsing plugin in the RaccoonConfig. you can check this link to see the implementation of the same.

You are now all set to use the library. Now the only things that’s left is that, we need to

  • Intercept the request with the request.
  • Convert it into RaccoonRequest.
  • Execute the request.

You can see the implementation of the interception here. Raccoon is already packed with the okhttp interception plugin.

If you don’t use an interceptor plugin, the you can see the plugin implementation to create your own interception logic.

Adding the interceptor plugin into your app’s code doesn’t start the mocking process unless you had initialize the RaccoonStub class as mentioned in the sample app. And always call tearDown() after each of the execution is completed or else, the Service-Controller-Endpoint remains in the memory and may give you some unexpected response that can fail the UI-Tests.

The possibilities go on, I’ve just touched the tip of the iceberg here. I do hope you benefited even slightly from this and are ready to introduce Raccoon into your next project.

If you enjoyed this, do let me know and I’ll gladly make a part two where I focus on some advanced tips/techniques and how we can use Raccoon in actual business projects. Thanks for reading!

refer to the github project for more insights.

Developer