Welcome to the second lesson in our exploration of Test Driven Development (TDD) with Swift and XCTest. Previously, you learned about using dummies to isolate dependencies. In this lesson, we'll focus on another type of test double — stubs.
By the end of this lesson, you'll understand what stubs are and how to implement them in your tests, specifically for isolating dependencies like external services.
Test doubles help us isolate parts of our application for testing. We've previously discussed dummies; now, let's move to a slightly more useful test double: stubs. Stubs are predefined answers to method calls made during testing. They don't track how they're used, unlike more complex test doubles that we'll learn about later.
Stubs are beneficial when you're testing a function that relies on an external service or complex dependency. They let you simulate the function's output, making your tests faster and more predictable. Keep in mind, though, that stubs primarily ensure your application logic functions as intended and don't necessarily verify the correctness of external dependencies. It’s also important to understand that overusing stubs can lead to tests that are tightly coupled to the stubbed behavior. If the real external service changes its behavior, your tests might still pass, giving a false sense of security. This is why stubs should be used to test your own logic, not to assume the correctness of services you don't control.
Stubs are used to provide consistent, predefined responses to specific method calls in tests, allowing you to control the behavior of certain dependencies without implementing their actual functionality. Unlike dummies, which are mere placeholders, stubs actively simulate responses, making them useful when you need predictable outcomes from dependencies. This predictability allows you to isolate and test your application's logic without needing to rely on the behavior of external systems, which is especially helpful when those systems may be complex or introduce variability.
Let’s apply the TDD workflow to build a WeatherAlertService
using stubs.
We are building a Weather Alert Service that will get its data from a WeatherService
, which receives weather data and issues alerts based on certain conditions. The WeatherAlertService
will need to fetch data from an external data source, which is not feasible in a testing environment. For the tests that rely on data from the WeatherService
, we can define what the WeatherData
looks like.
The classes are structured as follows:
Create a test file named WeatherAlertServiceTests.swift
with the following test cases:
Here:
- We implement a stub,
WeatherServiceStub
, to simulate weather data. We add thesetWeather
method, which allows tests to pre-define the results from the service. - We write our first
XCTest
test to check ifWeatherAlertService
processes weather data correctly.
Run this test, and expect it to fail initially as WeatherAlertService
is not yet implemented to handle the specified conditions.
Implement the shouldSendAlert
method with minimal logic:
Create WeatherAlertService.swift
:
Run the tests again. The goal here is to pass the specific test scenario by implementing only the needed logic without covering more cases yet.
In this lesson, we focused on integrating stubs as a practical solution for isolating dependencies in your tests. Here's a quick recap of what we covered:
- Stubs provide predefined outputs, which help in testing functionalities that depend on external services.
- We worked through the Red-Green-Refactor cycle, emphasizing writing failing tests before developing logic and refining the implementation without changing its behavior.
Remember, the power of stubs lies in their ability to remove uncertainty from your tests. Use them to create deterministic test environments where you control the input and expected outcome. This technique becomes especially useful when you want to simulate rare conditions (e.g., extreme weather data) that are hard to reproduce in real systems.
Prepare to practice these concepts in the upcoming exercises, where you'll tackle various scenarios and get more comfortable with using stubs and the TDD approach. Keep experimenting with test doubles and enhancing your understanding and skill set in crafting well-tested, robust code.
