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

5. Unit Tests
Written by Fernando Sproviero

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

As mentioned in Chapter 4, “The Testing Pyramid,” unit tests verify how isolated parts of your application work. Before checking how things work together, you need to make sure the units of your application behave as expected.

In this chapter, you’ll:

  • Learn what unit tests are and what are the best places to use them.
  • Write unit tests using the test-driven development (TDD) pattern to learn these concepts in the context of TDD.

Throughout this chapter and Chapter 7, “Introduction to Mockito” you’ll work on an application named Cocktail Game. With this application, you’ll have fun with a trivia game about cocktails.

Find the starter project for this application in the materials for this chapter and open it in Android Studio. Build and run the application. You’ll see a blank screen.

You’ll start writing tests and classes for the application and, by the end of Chapter 7, “Introduction to Mockito,” the application will look like this:

Game Screen
Game Screen

When to use unit tests

Unit tests are the fastest and easiest tests to write. They also are the quickest to run. When you want to ensure that a class or method is working as intended in isolation — this means with no other dependent classes — you write unit tests.

Before writing any feature code, you should first write a unit test for one of the classes that will compose your feature. Afterwards, you write the class that will pass the test. After repeating this procedure, you’ll have a completed, testable feature.

Setting up JUnit

You’re going to write a unit test for the first class of the cocktail game, which is a Game class. This first test will be a JUnit test, so, open app/build.gradle and add the following dependency:

dependencies {
  ...
  testImplementation 'junit:junit:4.12'
}

Creating unit tests

To start, switch to the Project View and open app ‣ src. Create a new directory and enter: test/java/com/raywenderlich/android/cocktails/game/model. Then, create a file called GameUnitTests.kt.

class GameUnitTests {
  // 1
  @Test
  fun whenIncrementingScore_shouldIncrementCurrentScore() {
    // 2
    val game = Game()

    // 3
    game.incrementScore()

    // 4
    Assert.assertEquals(1, game.currentScore)
  }
}
Assert.assertEquals("Current score should have been 1",
  1, game.currentScore)

Making the test compile

The test won’t compile because the Game class doesn’t exist. So, create the Game class under the directory app ‣ src ‣ main ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails ‣ game ‣ model. You’ll need to create game and model packages first. In the Game class, write the minimum amount of code to make the test compile:

class Game() {
  var currentScore = 0
    private set

  fun incrementScore() {
    // No implementation yet
  }
}

Running the test

Now, go back to the test; you’ll see that it compiles.

Making the test pass

Modify the Game class to make it pass:

class Game() {
  var currentScore = 0
    private set

  fun incrementScore() {
    currentScore++
  }
}

Creating more tests

The game will show a highest score. So, you should add a test that checks that when the current score is above the highest score, it increments the highest score:

@Test
fun whenIncrementingScore_aboveHighScore_shouldAlsoIncrementHighScore() {
  val game = Game()

  game.incrementScore()

  Assert.assertEquals(1, game.highestScore)
}
  var highestScore = 0
    private set

  fun incrementScore() {
    currentScore++
    highestScore++
  }

  @Test
  fun whenIncrementingScore_belowHighScore_shouldNotIncrementHighScore() {
    val game = Game(10)

    game.incrementScore()

    Assert.assertEquals(10, game.highestScore)
  }
class Game(highest: Int = 0) {
 var highestScore = highest
    private set

fun incrementScore() {
  currentScore++
  if (currentScore > highestScore) {
    highestScore = currentScore
  }
}

JUnit annotations

For this project, you’re creating a trivia game. Trivias have questions, so you’ll now create unit tests that model a question with two possible answers. The question also has an “answered” option to model what the user has answered to the question. Create a file called QuestionUnitTests.kt in the app ‣ src ‣ test ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails ‣ game ‣ model directory.

class QuestionUnitTests {

  @Test
  fun whenCreatingQuestion_shouldNotHaveAnsweredOption() {
    val question = Question("CORRECT", "INCORRECT")

    Assert.assertNull(question.answeredOption)
  }
}
class Question(val correctOption: String,
               val incorrectOption: String) {
  var answeredOption: String? = "MY ANSWER"
    private set
}

class Question(val correctOption: String,
               val incorrectOption: String) {
  var answeredOption: String? = null
    private set
}

  @Test
  fun whenAnswering_shouldHaveAnsweredOption() {
    val question = Question("CORRECT", "INCORRECT")

    question.answer("INCORRECT")

    Assert.assertEquals("INCORRECT", question.answeredOption)
  }
  fun answer(option: String) {
    // No implementation yet
  }

  fun answer(option: String) {
    answeredOption = option
  }

  @Test
  fun whenAnswering_withCorrectOption_shouldReturnTrue() {
    val question = Question("CORRECT", "INCORRECT")

    val result = question.answer("CORRECT")

    Assert.assertTrue(result)
  }
  fun answer(option: String): Boolean {
    answeredOption = option

    return false
  }

  fun answer(option: String): Boolean {
    answeredOption = option

    return true
  }

  @Test
  fun whenAnswering_withIncorrectOption_shouldReturnFalse() {
    val question = Question("CORRECT", "INCORRECT")

    val result = question.answer("INCORRECT")

    Assert.assertFalse(result)
  }

  fun answer(option: String): Boolean {
    answeredOption = option

    return correctOption == answeredOption
  }

  @Test(expected = IllegalArgumentException::class)
  fun whenAnswering_withInvalidOption_shouldThrowException() {
    val question = Question("CORRECT", "INCORRECT")

    question.answer("INVALID")
  }

  fun answer(option: String): Boolean {
    if (option != correctOption && option != incorrectOption)
      throw IllegalArgumentException("Not a valid option")

    answeredOption = option

    return correctOption == answeredOption
  }

  val isAnsweredCorrectly: Boolean
    get() = correctOption == answeredOption

  fun answer(option: String): Boolean {
    if (option != correctOption && option != incorrectOption)
      throw IllegalArgumentException("Not a valid option")

    answeredOption = option

    return isAnsweredCorrectly
  }

Refactoring the unit tests

Notice that each test repeats this line of code:

  val question = Question("CORRECT", "INCORRECT")
  private lateinit var question: Question

  @Before
  fun setup() {
    question = Question("CORRECT", "INCORRECT")
  }
  @Test
  fun whenCreatingQuestion_shouldNotHaveAnsweredOption() {
    Assert.assertNull(question.answeredOption)
  }

  @Test
  fun whenAnswering_shouldHaveAnsweredOption() {
    question.answer("INCORRECT")

    Assert.assertEquals("INCORRECT", question.answeredOption)
  }

  @Test
  fun whenAnswering_withCorrectOption_shouldReturnTrue() {
    val result = question.answer("CORRECT")

    Assert.assertTrue(result)
  }

  @Test
  fun whenAnswering_withIncorrectOption_shouldReturnFalse() {
    val result = question.answer("INCORRECT")

    Assert.assertFalse(result)
  }

  @Test(expected = IllegalArgumentException::class)
  fun whenAnswering_withInvalidOption_shouldThrowException() {
    question.answer("INVALID")
  }

Challenge

Challenge: Testing questions

You have the Game and Question classes. The Game class should contain a list of questions. For now, these are the requirements:

Key points

  • Unit tests verify how isolated parts of your application work.
  • Using JUnit, you can write unit tests asserting results, meaning, you can compare an expected result with the actual one.
  • Every test has three phases: set up, assertion and teardown.
  • In TDD, you start by writing a test. You then write the code to make the test compile. Next you see that the test fails. Finally, you add the implementation to the method under test to make it pass.

Where to go from here?

Great! You’ve just learned the basics of unit testing with JUnit. You can check the project materials for the final version of the code for this chapter.

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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now