Testing Network Layer Using TDD Approach with Android by Tutorial

Mohammed Abdullah
MindOrks
Published in
7 min readOct 18, 2019

There are processes for different ways to incorporate tests into your codebase, one of which is Test-Driven Development.

What is TDD?

TDD is a process in which you write the tests for the code you are going to add or modify before you write the actual code. Because it’s a process and not a library, you can apply it to any project, be it Android, iOS, web or anything else

Why is TDD important?

1- Write intentionally: Well-written tests provide a description of what your code should do. From the start, you will focus on the end result. Writing these specifications as tests can keep the result from deviating from the initial idea.

2- Keep maintainable code: When practicing TDD, it encourages you to pay attention to the structure of your code. You will want to architect your app in a testable way, which is generally cleaner and easier to maintain and read.

In this tutorial, you will learn how to write predictable network tests working on the Movie App

Note: This tutorial assumes that you have basic knowledge of Kotlin and Android

Getting start

To start, clone the Starter Project and open it in Android Studio

git clone https://github.com/mohammedgmgn/MovieApp-Clean-Architecture/tree/TDD_start

you can also clone the final source code

TDD Steps

1- Start any new task by writing a failing test. This is for any new feature, behavior change, or bug fix that doesn’t already have a test for what you will be doing. Make sure you run the test and see it fail.

2- Write the minimum code to make the test pass. Write the needs for that test, then run the test to see it pass.

What we will be going to Do?

1- Testing the endpoint (Movie Service)

2- Testing Movie Repository

Creating your test file

You can’t write tests without a place to put them! First off, create your test file. Create

MovieServiceTest.kt

Notice that this test is under test and not android test

Using MockWebServer

This is a library from OkHttp that allows you to run a local HTTP server in your tests. With it, you can specify what you want the server to return and perform verifications on the requests made. The dependency on MockWebServer is already added to the project for you.

To start, you need a test class. Add this empty class to your test file:

class MovieServiceTestUsingMockWebServer{}

Setting up MockWebServer

MockWebServer has a test rule you can use for your network tests. It is a scriptable web server. You will supply it responses and it will return them on request. Add the rule to your test class:

@get:Rule
val mockWebServer = MockWebServer()

Now, you’ll set up your MovieService to test. Because you’re using Retrofit, you’ll use a Retrofit.Builder to set it up. Add the following to your test class:

private val retrofit by lazy {
Retrofit.Builder()
//1
.baseUrl(mockWebServer.url("/"))
//2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//3
.addConverterFactory(GsonConverterFactory.create())
//4
.build()

}

In the above, you:

1. Set the baseUrl on the builder using the mockWebServer. This is required when using Retrofit. Because you’re not hitting the network, “/” is perfectly valid, here.

2. Add a call adapter. Using a RxJava call adapter allows you to return RxJava streams in your MovieService, helping you handle the asynchronous nature of the network calls. Don’t worry you don’t need to be a RxExpert to keep going!

3. Add a converter factory. This is so you can use Gson to automatically convert the JSON to a nice Kotlin object.

4. Build it!

You then use retrofit to create your MovieService! Add this code:

private val movieService by lazy {
retrofit
.create(MovieService::class.java)
}

Running the MockWebServer

This test for getPopularMovies() will be called testPopularMovies() to match
the described functionality. Add the test function to your class :

@Test
fun testPopularMovies() {
}

There’s nothing in it yet but run the test anyway. There’s some interesting output in the console

The MockWebServer starts up at the beginning of your test and closes at the end. Anywhere in-between the beginning and end, you can script requests, make those requests to get the response, and perform verifications

Scripting a response

Now that you have MockWebServer set up to receive requests, it’s time to script something for it to return!

There are two ways to set up the JSON to script a response with. One way is to pull in the JSON from a file. This is a great option if you have a long-expected response or you have a real response from a request that you want to copy-paste in. You will use the first way in this tutorial by Scripting the JSON response from a file

For now, it’s time to learn how to use this JSON to script a response!

Add this to your empty testPopularMovies() test:

// 1
mockWebServer.enqueue(
// 2
MockResponse()
// 3
.setBody(MovieTestUtils.getJson("popular_movies_response.json"))
// 4
.setResponseCode(200)

)

Going over these step-by-step:

1. Use the mockWebServer that you created before to enqueue a response.

2. You enqueue a response by building and passing in a MockResponse object.

3. Use the testJson that you loaded as the body of the response.

4. Set the response code to 200 — success!

Writing a MockWebServer test

With all that setup, it’s finally time to finish writing your test. Add these two lines to the bottom of the testPopularMovies(). There will be an error at getPopularMovies() because you haven’t created it yet

//1
val testObserver = movieService.getPopularMovies().test()
//2
TestCase.assertEquals(testObserver.values()[0].movies, MovieTestUtils.getMovieTestObject().movies)

Here, you:

1. Call getPopularMovies() on your MovieService. By chaining test() you get a TestObserver that you can use to verify the value of the Single that getPopularMovies() returns.

2. Verify that the value that returns is a Movies list with the same values you placed in the testJson and enqueued with MockWebServer

Next step of the TDD process: Write just enough code so you can compile and run your test. Add getPopularMovies() to the MovieService interface with a return value of Single<MovieListResult>:

fun getPopularMovies(): Single<MovieListResult>

Build and run your test! if you have some familiarity with Retrofit, you’ll get an error that Retrofit requires an HTTP method annotation for your new method

Your goal is to make this run, so next, you add an annotation! Add the @GET
annotation to your MovieService method:

@GET(Endpoint.DISCOVER_MOVIES)
fun getPopularMovies(): Single<MovieListResult>

Run it, and you’re all green!

MockWebServer does help you test the endpoint path too! Next you’ll add a test that the endpoint path is correct

Add the following test to your test class:

@Test
fun testMoviesServicePath() {
//1
mockWebServer.enqueue(
MockResponse()
.setBody(MovieTestUtils.getJson("popular_movies_response.json"))
.setResponseCode(200))
//2
val testObserver = movieService.getPopularMovies().test()
//3
testObserver.assertNoErrors()
//4
TestCase.assertEquals(Endpoint.VALID_EXPECTED_PATH, mockWebServer.takeRequest().path)
}

Here’s what’s happening:

1. Enqueue the response, same as before.

2. Call getPopularMovies() as before, getting a reference to a TestObserver.

3. You can also use the testObserver to make sure there were no errors emitted.

4. Here’s what you’re writing this for! You can use the mockWebServer to get the path that was requested to compare it to what you expect.

Run the test, and you’re right! It fails!

Update the @GET annotation once more to make this pass:

@GET(Endpoint.VALID_EXPECTED_PATH)

Build and run your test. It passes! You now know your MovieService uses the correct endpoint

Mocking the service

In this test, you will also be concerned with getPopularMovies(), but your test will worry more about its interaction with the respository.

To start, create a new test class to write your Mockito tests in

class MovieRepositoryImplTests {
}

Next, you need to set up your test subject. Add this to your MovieRepositoryImplTests class. When prompted, import com.nhaarman.mockitokotlin2.mock:

private val movieService: MovieService = mock()
private val repository = RepositoryImpl(movieService)

Here, you’re mocking the MovieService, which you then pass into the constructor of RepositoryImpl

Now, the test you’ve been waiting for! Add this test method:

@Test
fun getTestMovieResponse() {
val gson = Gson()
//1
val testModel = gson.fromJson(MovieTestUtils.getJson("popular_movies_response.json"), MovieListResult::class.java)
//2
whenever
(movieService.getPopularMovies())
.thenReturn(Single.just(testModel))
//3
val testObserver = repository.getMovies().test()
//4
testObserver.assertValue(testModel)
}

Here, you:

1. Create the MovieResult object to use in this test.

2. Set up your MovieService mock to return a Single with that MovieResult when getPopularMovies() is called.

3. The signature of getMovies() on the repository also returns a Single. Use the same TestObserver strategy you used before here.

4. Assert that the repository’s getMovies() emits the same result that comes from the MovieService you mocked.

Run your test and see it fail:

What else now but to make it pass! Open up Repository.kt. Change the body of
getMovies() in RepositoryImpl to be the following:

return  service.getPopularMovies()

Now, you’re calling the MovieService as expected. Run that test and see it pass this time!

you can clone the complete source code

git clone  https://github.com/mohammedgmgn/MovieApp-Clean-Architecture/tree/TDD_final

Key Points

  • You can use MockWebServer to script request responses and verify that the correct endpoint was called.
  • You can mock the network layer with Mockito if you don’t need the fine-grained control of MockWebServer.
  • If you’re writing tests along side the code you’re writing, you’re going to have more test coverage over the code! This is important to many organizations

LinkedIn : https://www.linkedin.com/in/mohammed-mahmoud-/

--

--

Mohammed Abdullah
MindOrks

Android Developer who always looks both ways before crossing a one-way street