Chapters

Hide chapters

Android Test-Driven Development by Tutorials

First Edition · Android 10 · Kotlin 1.3 · AS 3.5

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Testing on a New Project

Section 2: 8 chapters
Show chapters Hide chapters

Section III: TDD on Legacy Projects

Section 3: 9 chapters
Show chapters Hide chapters

10. Testing the Network Layer
Written by Victoria Gonda

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You live in a time of the internet, and it’s likely that your app does, too. Most apps connect to an API over the network in one way or another, giving them a network layer. As this is often a critical part of your app, it stands to reason that you should test it. This is what you’ll learn to test in this chapter! Along the way you’ll learn:

  • Tools for testing your network layer.
  • How to provide reliable test data.
  • Important things to know about maintaining network layer tests.

One thing to consider when testing your interaction with data across a network is that, when running your automated tests, you don’t actually want to make a network call. Network calls are unpredictable. A call can fail because of the network connection, the server, the internet service provider and so on. You can’t always know the state of any of these components. You need your tests to be repeatable and predictable, so this dependency on a wobbly network won’t work, here.

There are tools that you can use to test your network layer without hitting the network, which is what you will focus on in this chapter. You will look at three tools to add to your testing toolbox:

  • MockWebserver to mock the responses from network requests
  • Mockito to mock your API responses
  • Faker for data creation

Getting started

In this chapter, you will work on the network layer for an app called Punchline. This is an app that will show you a new, random joke every time you press a button. To start, find the starter project in the materials for this chapter and open it in Android Studio.

Run the app, but you won’t see much yet:

There will be no UI for you to play with until the end of Chapter 11, “User Interface.” Until then, you’ll see your progress made in the form of green tests for the network layer!

The Punchline app has a single call to the network: the one that fetches a random joke. You will test this request three times, each using a different tool. There are a couple of files that you should have on hand before you get started with your tests:

  • JokeService.kt: This is the Retrofit service that you will declare your network requests in.

  • Repository.kt: This file defines RepositoryImpl. It’s the glue that connects the network layer with the rest of the app.

  • Joke.kt: Your Joke data model lives here. You can see that it has values for the ID and joke.

Note: Is it helpful, but not required, to have a familiarity with Retrofit for this chapter. To learn more, go to “Android Networking Tutorial: Getting Started” https://www.raywenderlich.com/2-android-networking-tutorial-getting-started.

Creating your test file

You can’t write tests without a place to put them! First off, create your test file. Create JokeServiceTest.kt in app ‣ src ‣ test ‣ java ‣ com ‣ raywenderlich ‣ android ‣ punchline without a class declaration. You’ll put all three of your test classes in this file for easy comparison. Notice that this test is under test and not androidTest. That’s right, you don’t need the Android framework to test your network layer when using these tools! They’ll run nice and fast. You can also use MockWebServer in your Android tests if there’s a place you want to use it in an integration test in your own apps.

Investigating the API

Most of the Retrofit boilerplate is already set up for you. You can peek at KoinModules.kt if you’re interested in seeing that set up. What you care about before you test is the specific endpoint you are testing and implementing.

{
  "id":17,
  "joke":"Where do programmers like to hangout? The Foo Bar.",
  "created_at":"2018-12-31T21:08:53.772Z",
  "updated_at":"2018-12-31T21:36:33.937Z",
  "url":"https://rw-punchline.herokuapp.com/jokes/17.json"
}

Using MockWebServer

The first tool you will learn is 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.

testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0"
class JokeServiceTestUsingMockWebServer {
}

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()
private val retrofit by lazy {
  Retrofit.Builder()
      // 1
      .baseUrl(mockWebServer.url("/"))
      // 2
      .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
      // 3
      .addConverterFactory(GsonConverterFactory.create())
      // 4
      .build()
}
private val jokeService by lazy {
  retrofit.create(JokeService::class.java)
}

Running the MockWebServer

Your JokeService should have a function, getRandomJoke(), that returns a random joke. To handle the asynchronous nature of network calls, you will use RxJava. If you’re not familiar with RxJava, this is all you need to know: getRandomJoke() will return a Single of type Joke. Parties will subscribe to this Single and receive an event when it emits a Joke. RxJava brings a lot of power, but, for the sake of this exercise, you can think of it as a way to perform a callback.

@Test
fun getRandomJokeEmitsJoke() {
}

Scripting a response

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

private val testJson = """{ "id": 1, "joke": "joke" }"""
// 1
mockWebServer.enqueue(
    // 2
    MockResponse()
        // 3
        .setBody(testJson)
        // 4
        .setResponseCode(200))

Writing a MockWebServer test

Phew! With all that set up, it’s finally time to finish writing your test. Add these two lines to the bottom of getRandomJokeEmitsJoke(). There will be an error at getRandomJoke() because you haven’t created it yet:

// 1
val testObserver = jokeService.getRandomJoke().test()
// 2
testObserver.assertValue(Joke("1", "joke"))
fun getRandomJoke(): Single<Joke>

@GET("https://raywenderlich.com")
fun getRandomJoke(): Single<Joke>

@GET("joke.json")

Refactoring your test

You may feel like there’s a code-smell in the way you’re hard coding the values for the ID and joke. Thankfully, you can change that! Because you’re creating the JSON String in the test, you can create it the way you like. Make a constant for the ID and the joke. By putting them outside the test class at the file level you’ll be able to use them in your other tests too:

private const val id = "6"
private const val joke =
    "How does a train eat? It goes chew, chew"
private val testJson = """{ "id": $id, "joke": "$joke" }"""
testObserver.assertValue(Joke(id, joke))

Maintaining test data

You just set up some test data and wrote some tests. Now, imagine the JSON response had many more properties and you had more endpoints to test. Then, imagine the format of the response changed. Maybe instead of joke as a String, it contained an object with different translations of the joke. You need to make sure you update your tests with this change when it happens. If you don’t test the new type of response, your tests are no longer accurate.

Testing the endpoint

You may be having some doubts about that last test. If it will pass with any endpoint with the same base URL, what is it testing? Is it testing that the response is correctly parsed into a Joke object? While it’s important to know your data is represented correctly, MockWebServer does help you test the endpoint too! Next you’ll add a test that the endpoint is correct.

@Test
fun getRandomJokeGetsRandomJokeJson() {
  // 1
  mockWebServer.enqueue(
      MockResponse()
          .setBody(testJson)
          .setResponseCode(200))
  // 2
  val testObserver = jokeService.getRandomJoke().test()
  // 3
  testObserver.assertNoErrors()
  // 4
  assertEquals("/random_joke.json",
      mockWebServer.takeRequest().path)
}

@GET("random_joke.json")

Mocking the service

Depending on your app and your team, it may be enough to know that the service methods are available and you’re using them correctly. This can be done using Mockito, which you first learned in Chapter 7, “Introduction to Mockito.” In this test you will also be concerned with getRandomJoke(), but your test will worry more about its interaction with the respository.

class JokeServiceTestMockingService {
}
private val jokeService: JokeService = mock()
private val repository = RepositoryImpl(jokeService)
@Test
fun getRandomJokeEmitsJoke() {
  // 1
  val joke = Joke(id, joke)
  // 2
  whenever(jokeService.getRandomJoke())
      .thenReturn(Single.just(joke))
  // 3
  val testObserver = repository.getJoke().test()
  // 4
  testObserver.assertValue(joke)
}

return service.getRandomJoke()

Using Faker for test data

Throughout this chapter, you’ve been using the same boring old test data:

private const val id = "6"
private const val joke =
    "How does a train eat? It goes chew, chew"
testImplementation 'com.github.javafaker:javafaker:0.16'
class JokeServiceTestUsingFaker {
}
var faker = Faker()
private val jokeService: JokeService = mock()
private val repository = RepositoryImpl(jokeService)
@Test
fun getRandomJokeEmitsJoke() {
  val joke = Joke(
      faker.idNumber().valid(),
      faker.lorem().sentence())

  whenever(jokeService.getRandomJoke())
      .thenReturn(Single.just(joke))
  val testObserver = repository.getJoke().test()

  testObserver.assertValue(joke)
}

Deciding what tools to use

You’ve learned so many tools in this chapter and in the previous chapters. How do you decide which to use? When is a unit test best or when is it time for an integration test? Hopefully, you’re starting to think about how you can mix and match some of these things, such as how you used both Faker and Mockito for your last test.

Key points

  • To keep your tests repeatable and predictable, you shouldn’t make real network calls in your tests.
  • You can use MockWebServer to script request responses and verify that the correct endpoint was called.
  • How you create and maintain test data will change depending on the needs for your app.
  • You can mock the network layer with Mockito if you don’t need the fine-grained control of MockWebServer.
  • By using the Faker library you can easily create random, interesting test data.
  • Deciding which tools are right for the job takes time to learn through experiment, trial, and error.

Where to go from here?

You’ve come so far! From unit tests to integration, testing so many parts of your apps. You still have a very important part to learn how to test: the User Interface! All the things you’ve been learning are leading up to this point. In Chapter 11, “User Interface,” you’ll learn how to automate those clicks on your screen and to verify what the user sees.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now