Utilizing Stubs for Test-Driven Development

Introduction and Context Setting

Welcome to the second lesson in our exploration of Test-Driven Development (TDD) using Rust. In the previous lesson, we delved into the use of dummies to isolate dependencies in tests. This lesson will shift our focus to another vital kind of test double — Stubs.

By the end of this lesson, you will understand what stubs are and how to implement them in your tests using Rust. This technique is particularly useful for isolating dependencies like external services, making your tests more reliable and manageable.

Understanding Stubs in Testing

In testing, test doubles help us isolate parts of our application. We've already covered dummies. Stubs, which are a more active type of test double, allow us to provide predefined responses to method calls during testing. Unlike other test doubles, stubs don’t track interactions but are ideal for certain scenarios.

Stubs are especially useful when testing functions that depend on external services or complex dependencies. They allow us to simulate function outputs, making tests faster and more predictable. Stubs focus on ensuring your application's logic operates as expected without checking the correctness of external dependencies.

Example: Crafting a WeatherAlertService Using Stubs

To demonstrate stubs, we will create a WeatherAlertService using stubs in a TDD process with Rust.

We'll develop a WeatherAlertService that fetches data from a WeatherService. This service will issue alerts based on specific conditions. Utilizing the actual data source is impractical for testing, so we’ll use stubbed data instead.

Red: Writing the First Test

Create a new test module in your Rust project and set up the following test:

#[cfg(test)]
mod tests {
    use project::*;

    struct WeatherServiceStub {
        temperature: i32,
        conditions: String,
    }

    impl WeatherServiceStub {
        fn set_weather(&mut self, temperature: i32, conditions: &str) {
            self.temperature = temperature;
            self.conditions = conditions.to_string();
        }

        fn get_current_weather(&self, _location: &str) -> WeatherData {
            WeatherData {
                temperature: self.temperature,
                conditions: self.conditions.clone(),
            }
        }
    }

    struct WeatherData {
        temperature: i32,
        conditions: String,
    }

    struct WeatherAlertService<'a> {
        weather_service: &'a dyn WeatherService,
    }

    impl<'a> WeatherAlertService<'a> {
        fn should_send_alert(&self, location: &str) -> String {
            let weather = self.weather_service.get_current_weather(location);
            if weather.temperature > 35 {
                "Extreme heat warning. Stay hydrated!".to_string()
            } else {
                "No alert".to_string()
            }
        }
    }

    trait WeatherService {
        fn get_current_weather(&self, location: &str) -> WeatherData;
    }

    #[test]
    fn should_return_heat_warning_when_temperature_is_above_35() {
        // Arrange
        let mut weather_service = WeatherServiceStub {
            temperature: 0,
            conditions: String::new(),
        };
        weather_service.set_weather(36, "sunny");
        let alert_service = WeatherAlertService {
            weather_service: &weather_service,
        };

        // Act
        let alert = alert_service.should_send_alert("London");

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

In this test:

  • We create a manual stub, WeatherServiceStub, that simulates weather data by setting predefined values for temperature and conditions via the set_weather method.
  • The stub's get_current_weather method returns a WeatherData struct with these predefined values, simulating expected weather conditions for the test scenario.
  • Using Rust's test framework, we assert whether the WeatherAlertService correctly interprets the weather data and returns the appropriate alert, specifically checking for a heat warning when the temperature exceeds 35 degrees.

Run this test, expecting it to initially fail, as WeatherAlertService does not yet appropriately implement the logic for handling these 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