Welcome to the second lesson in our exploration of Test-Driven Development (TDD) using Scala 3, ScalaTest, 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 within a Scala environment, specifically for isolating dependencies like external services.
In testing, test doubles help us isolate parts of our application. We've previously discussed dummies; now, we will explore stubs, a more useful type of test double. 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 Scala, Mockito can be 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.
To illustrate the concept of stubs, we will create a WeatherAlertService
using stubs in a test-driven development process.
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.
Create a new test file named WeatherAlertServiceSpec.scala
with the following test setup:
In this test:
- We create a hand-crafted stub
WeatherServiceStub
that implements a traitIWeatherService
. This stub allows us to customize the weather data by setting predefined values fortemperature
andconditions
. - The stub’s
getCurrentWeather
method returns aWeatherData
object with these predefined values, simulating expected weather conditions for the test scenario without using Mockito. - The ScalaTest framework is used to validate 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.
Implement the WeatherAlertService
with it's supporting case class and trait as shown below:
Run the tests again. The objective is to pass the specific test scenario by implementing only the necessary logic without overengineering other cases.
In the initial implementation, we created a manual stub, WeatherServiceStub
, to simulate weather data. Now, we refactor by leveraging Mockito to create a stub more succinctly:
By using Mockito in Scala, we eliminate the need to manage state explicitly within a custom stub class, thus simplifying the test process. This approach helps you focus on behavior rather than on detailed setup.
In this lesson, we delved into the concept of stubs and how they serve as a practical method to isolate dependencies in tests. Here are the key takeaways:
- Stubs offer a way to replace external dependencies by providing predefined return values, facilitating the testing of functionalities reliant on services beyond our immediate control.
- Through the Red-Green-Refactor cycle, we emphasized writing tests that initially fail, implementing just enough logic to pass them, and then refining the code.
- Using Mockito in Scala allows us to create stubs (and later mocks) efficiently, leading to cleaner and more maintainable tests.
Prepare to apply these techniques in forthcoming exercises, where you'll explore different scenarios utilizing stubs. This will enhance your skills in employing test doubles and adhering to the TDD methodology, ultimately advancing your ability to create dependable, thoroughly tested code.
