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.