Introduction to Stubs in Testing with Kotlin

Introduction and Context Setting

Welcome to the second lesson in our exploration of Test-Driven Development (TDD) using Kotlin, JUnit, and Mockito. In the previous lesson, we covered how to utilize dummies to isolate dependencies. This lesson will focus on learning about another type of test double — Stubs.

By the end of this lesson, you will understand what stubs are and how to implement them in your tests, specifically for isolating dependencies like external services within the Kotlin ecosystem.

Understanding Stubs in Testing

Stubs provide predefined answers to method calls during testing. Unlike other test doubles, stubs do not track their usage, making them simpler yet powerful for certain scenarios.

Stubs are particularly useful when testing functions that rely on external services or complex dependencies. By simulating function outputs, stubs make tests faster and more predictable. Keep in mind that stubs focus on ensuring your application's logic functions as expected without verifying the correctness of external dependencies.

In Kotlin, Mockito is a popular library used to create stubs. It allows us to set up return values that simulate how dependencies should behave in a controlled environment. This predictability isolates and tests your application's logic without relying on the behavior of external systems, which might be complex or introduce variability.

Example: Crafting a `WeatherAlertService` Using Stubs

We will build a WeatherAlertService that fetches data from a WeatherService. This service will issue alerts based on specific conditions. The external data source is impractical for testing, so we'll use stubbed data for our tests instead.

Red: Writing the First Test

Create a new test class named WeatherAlertServiceTest.kt with the following test setup:

import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class WeatherAlertServiceTest {

    private lateinit var weatherService: WeatherServiceStub
    private lateinit var alertService: WeatherAlertService

    class WeatherServiceStub : IWeatherService {
        private var currentWeather: WeatherData = WeatherData(20, "sunny")

        fun setWeather(temperature: Int, conditions: String) {
            this.currentWeather = WeatherData(temperature, conditions)
        }

        override fun getCurrentWeather(location: String): WeatherData {
            return currentWeather
        }
    }

    @BeforeEach
    fun setUp() {
        weatherService = WeatherServiceStub()
        alertService = WeatherAlertService(weatherService)
    }

    @Test
    fun shouldReturnHeatWarningWhenTemperatureIsAbove35() {
        // Arrange
        weatherService.setWeather(36, "sunny")

        // Act
        val alert = alertService.shouldSendAlert("London")

        // Assert
        assertEquals("Extreme heat warning. Stay hydrated!", alert)
    }
}

In this test:

  • We create a hand-crafted stub WeatherServiceStub that implements the IWeatherService interface. This stub allows us to customize the weather data by setting predefined values for temperature and conditions via the setWeather method.
  • The stub’s getCurrentWeather method returns a WeatherData object with these predefined values, simulating expected weather conditions for the test scenario without using Mockito.
  • The JUnit test validates whether the WeatherAlertService correctly interprets the weather data and returns the appropriate alert, specifically checking if it generates a heat warning when the temperature exceeds 35 degrees.

Run this test, and expect it to fail initially, as WeatherAlertService is not yet implemented to handle the specified conditions.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal