Sling Academy
Home/Rust/Mocking Network Calls in Rust Tests with the surf or reqwest Crates

Mocking Network Calls in Rust Tests with the surf or reqwest Crates

Last updated: January 07, 2025

When developing in Rust, it's common to want to test your applications without making real network calls. Network requests can be unpredictable and often don't provide the ideal conditions for unit tests. This article will guide you through mocking network calls with Rust using the surf and reqwest crates. We will explore how to create reliable tests without hitting actual external services.

Why Mock Network Calls?

Mocking network calls in tests offer several benefits:

  • Speed: Tests run faster as they eliminate network latency.
  • Reliability: Tests do not depend on external server stability.
  • Cost: No outgoing requests can mean reduced usage and cost for APIs with usage-based billing.
  • Control: Easily simulate various responses and edge cases that might be hard to reproduce with real APIs.

Using surf for Mocking

The surf crate is a versatile HTTP client that can be used for both async and blocking requests. Here's how you can mock network calls using surf:

Setting Up Your Dependencies

First, add surf and httpmock to your Cargo.toml:

[dependencies]
surf = "^2.3"

[dev-dependencies]
httpmock = "^0.6"

Creating a Mock Server

Here’s a simple example:

use httpmock::MockServer;
use surf;

#[async_std::test]
async fn test_get_request() {
    let server = MockServer::start_async().await;

    let mock = server.mock(|when, then| {
        when.method(GET)
            .path("/user");
        then.status(200)
            .header("content-type", "application/json")
            .body(r#"{"name": "John", "age": 30}"#);
    }).await;

    let url = format!("{}/user", server.base_url());
    let mut res: surf::Response = surf::get(url).await.unwrap();
    let body = res.body_string().await.unwrap();
    assert_eq!(body, r#"{"name": "John", "age": 30}"#);

    mock.assert();
}

In this snippet, a mock server provides a static JSON response. An asynchronous test verifies the mocked response matches expected data.

Using reqwest for Mocking

The popular reqwest crate is another great option for making HTTP requests in Rust. Here's how to mock network requests with reqwest:

Setting Up Your Dependencies

Add reqwest and wiremock to your Cargo.toml to enable request mocking:

[dependencies]
reqwest = { version = "0.11", features = ["json"] }

[dev-dependencies]
wiremock = "^0.4"

Creating and Using Wiremock

The following example demonstrates request mocking with reqwest and wiremock for synchronous tests:

use reqwest;
use wiremock::{MockServer, ResponseTemplate, Mock, matchers::{method, path}};

#[tokio::test]
async fn test_fetch_user() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/users"))
        .respond_with(ResponseTemplate::new(200)
        .set_body_raw("{"name": "Alice", "occupation": "Engineer"}", "application/json"))
        .mount(&server)
        .await;

    let client = reqwest::Client::new();
    let url = format!("{}/users", server.uri());
    let res = client.get(&url).send().await.unwrap();
    let user: serde_json::Value = res.json().await.unwrap();
    assert_eq!(user["name"], "Alice");
}

This example utilizes wiremock's matching mechanism to assert that a GET request to /users returns a predefined JSON body. Both synchronous and asynchronous approaches can be applied depending on your project requirements.

Conclusion

Mocking network calls enable you to create fast, reliable tests free from external dependencies. By using tools like surf and reqwest in combination with mock environment crates like httpmock and wiremock, Rust developers can build robust unit tests and ensure they're only catching genuine issues in your codebase and not network variability. Both libraries showcase clear usage for testing HTTP requests efficiently with controlled conditions, highlighting the different approaches available for accomplishing similar goals.

Next Article: Managing Test Fixtures and Setup/Teardown Logic in Rust

Previous Article: Testing Performance Bottlenecks in Rust with Profilers and Benchmarks

Series: Testing in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior
  • Enforcing runtime invariants with generic phantom types in Rust