Course contentsShow
← Rust
Lesson 1650 of 1,91014. System Integration and EcosystemFree lesson

Mocking External Services

You will use traits and dependency injection to mock network services in unit and integration tests.

In this lesson, you will learn how to use traits and dependency injection to decouple your application logic from network-bound services, enabling fast and deterministic unit tests.

The Problem with Real Network Calls

When your application logic (e.g., a credit card processor or a weather API client) relies directly on concrete network implementations, your tests become fragile. They depend on internet connectivity, remote service uptime, and expensive API quotas. To solve this, we abstract the service behind a trait.

Dependency Injection via Traits

Instead of hardcoding a HttpClient inside your business logic, you define a trait representing the required functionality and inject an implementation of that trait into your structures.

// The abstraction
pub trait PaymentGateway {
    fn charge(&self, amount: u32) -> Result<(), String>;
}

// Your business logic
pub struct CheckoutService<T: PaymentGateway> {
    gateway: T,
}

impl<T: PaymentGateway> CheckoutService<T> {
    pub fn process(&self, amount: u32) -> Result<(), String> {
        self.gateway.charge(amount)
    }
}

Mocking for Tests

In your test suite, you don't need a real network client. You simply create a "mock" struct that implements your trait, allowing you to control the return values precisely and verify that your service handles both successes and failures correctly.

#[cfg(test)]
struct MockGateway {
    should_fail: bool,
}

impl PaymentGateway for MockGateway {
    fn charge(&self, _amount: u32) -> Result<(), String> {
        if self.should_fail { Err("Network error".into()) } else { Ok(()) }
    }
}

#[test]
fn test_checkout_failure() {
    let mock = MockGateway { should_fail: true };
    let service = CheckoutService { gateway: mock };
    assert!(service.process(100).is_err());
}

By using generics (T: PaymentGateway), you can swap the real implementation in production with a mock implementation in tests. This pattern—Dependency Injection—ensures your business logic remains agnostic of the underlying transport layer. When integrating with external systems, keep the network-specific code in one crate and the trait definitions in another to keep your unit tests clean and lightning-fast.

Key Takeaway: Decouple your business logic from network side-effects by defining an abstraction trait and injecting the implementation, allowing you to swap production clients for predictable, local mocks during testing.