HTTP Mock Testing Guide
This guide explains how to use the HTTP mock infrastructure for testing API integrations in the Runway Calculator application.
Overview
The HTTP mock infrastructure allows you to:
- Intercept fetch requests made by the application
- Return predefined responses for testing different scenarios
- Verify the requests made by the application
- Test error conditions and network failures
- Test timing-sensitive behavior with delayed responses
Basic Usage
Setting Up a Mock
use yew_wasm_starter_runway_calculator::tests::api_integration::http_mock::HttpMockInterceptor;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
async fn test_api_integration() {
// Create a new mock interceptor
let http_mock = HttpMockInterceptor::new();
// Configure mock responses
http_mock.add_response(
"api/users", // URL pattern to match
200, // HTTP status code
r#"{"users": [{"id": 1, "name": "Test User"}]}"# // Response body
);
// Install the mock (intercepts fetch calls)
http_mock.install().expect("Failed to install HTTP mock");
// Make API calls that will be intercepted by the mock...
// Uninstall mock when done
http_mock.uninstall().expect("Failed to uninstall HTTP mock");
}
Configuration Options
For more control, use add_response_with_config:
http_mock.add_response_with_config(
"api/auth", // URL pattern
Some("POST"), // HTTP method (optional)
401, // Status code
r#"{"error": "Unauthorized"}"#, // Response body
vec![("Content-Type", "application/json")], // Headers
Some(100) // Response delay in ms (optional)
);
Advanced Features
Schema Validation (NEW!)
The HTTP mock now supports schema validation for request bodies, allowing you to test that your API calls send properly structured data:
// Define a schema for user registration
let schema = r#"{
"type": "object",
"required": ["username", "password", "email"],
"properties": {
"username": { "type": "string" },
"password": { "type": "string" },
"email": { "type": "string" }
}
}"#;
// Add schema validation for a specific endpoint
http_mock.add_schema_validation(
"api/users/register",
Some("POST"),
schema
).expect("Failed to add schema validation");
// Add a success response for when validation passes
http_mock.add_response_with_config(
"api/users/register",
Some("POST"),
201,
r#"{"success": true, "message": "User registered successfully"}"#,
vec![("Content-Type", "application/json")],
None
);
// This request will fail validation because it's missing required fields
let invalid_response = Request::post("api/users/register")
.json(&serde_json::json!({
"username": "testuser"
// Missing password and email
}))
.send()
.await;
// Will return a 400 Bad Request response
assert_eq!(invalid_response.unwrap().status(), 400);
// This request will pass validation
let valid_response = Request::post("api/users/register")
.json(&serde_json::json!({
"username": "testuser",
"password": "password123",
"email": "test@example.com"
}))
.send()
.await;
// Will return a 201 Created response
assert_eq!(valid_response.unwrap().status(), 201);
Custom Validation Errors
You can customize the error response when validation fails:
http_mock.add_schema_validation_with_error(
"api/auth/login",
Some("POST"),
schema,
422, // Unprocessable Entity
"Invalid login data format. Email and password are required."
).expect("Failed to add schema validation");
Nested Object Validation
The validator supports nested objects:
let schema = r#"{
"type": "object",
"required": ["user", "settings"],
"properties": {
"user": {
"type": "object",
"required": ["name", "email"],
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
}
},
"settings": {
"type": "object",
"properties": {
"notifications": { "type": "boolean" }
}
}
}
}"#;
For more examples, see the tests in the tests/enhanced_http_mock_test.rs file.
Delayed Responses
Testing loading states and timeouts:
// Add a response with 500ms delay
http_mock.add_delayed_response(
"api/slow-endpoint",
200,
r#"{"data": "response after delay"}"#,
500 // milliseconds
);
// Measure the time taken
let start_time = js_sys::Date::now();
let response = api_client.fetch("api/slow-endpoint").await;
let end_time = js_sys::Date::now();
let elapsed = end_time - start_time;
// Verify the delay
assert!(elapsed >= 400.0, "Response should be delayed");
Network Failures
Testing error handling for network failures:
// Simulate network failure
http_mock.simulate_network_failure("/api/network-error", Some("GET"));
// This request should fail
let result = api_client.fetch("/api/network-error").await;
assert!(result.is_err(), "Request should fail with network error");
Error Responses
Testing error handling with different status codes:
// Add different error responses
http_mock.add_error_response(
"/api/not-found",
404,
"Resource not found"
);
http_mock.add_error_response(
"/api/server-error",
500,
"Internal server error"
);
// Test 404 handling
let response = api_client.fetch("/api/not-found").await;
assert_eq!(response.status(), 404);
// Test 500 handling
let response = api_client.fetch("/api/server-error").await;
assert_eq!(response.status(), 500);
Request Verification
Checking that the right requests were made:
// Make API call
api_client.post_json(
"/api/users",
r#"{"username": "new_user", "email": "user@example.com"}"#
).await;
// Check requests were made correctly
let requests = http_mock.get_requests_matching("/api/users");
assert_eq!(requests.len(), 1, "Should have made one request");
// Verify specific content
assert!(
http_mock.verify_request_contains("/api/users", "new_user"),
"Request should contain username"
);
Testing Patterns
Authentication Testing
Testing login flow:
// Set up mock responses for login
http_mock.add_response_with_config(
"api/auth/login",
Some("POST"),
200,
r#"{"token": "jwt.token.here", "user": {"id": 1, "name": "Test User"}}"#,
vec![("Content-Type", "application/json")],
None
);
// Test login functionality
let auth_service = AuthService::new(client);
let result = auth_service.login("username", "password").await;
// Verify successful login
assert!(result.is_ok());
assert!(auth_service.is_authenticated());
Error Handling Test
Testing proper error handling:
// Set up mock for server error
http_mock.add_error_response(
"api/scenarios",
500,
"Database connection failed"
);
// Attempt to fetch scenarios
let result = scenario_service.get_all_scenarios().await;
// Verify proper error handling
assert!(result.is_err());
let error = result.err().unwrap();
assert!(error.contains("Database connection failed"));
Offline/Online Synchronization Testing
Testing behavior with network transitions:
// Initial online state
js_sys::eval("window.navigator.onLine = true;").unwrap();
// Set up sync response
http_mock.add_response_with_config(
"api/sync",
Some("POST"),
200,
r#"{"success": true, "data": [...]}"#,
vec![],
None
);
// Successful sync while online
let online_result = sync_service.sync_data().await;
assert!(online_result.is_ok());
// Switch to offline
js_sys::eval("window.navigator.onLine = false;").unwrap();
// Attempt sync while offline
let offline_result = sync_service.sync_data().await;
assert!(offline_result.is_err());
Tips and Best Practices
-
Always uninstall the mock when you're done with it to avoid interfering with other tests.
-
Clean up any state (localStorage, sessionStorage) between tests to avoid test contamination.
-
Be specific with URL patterns to avoid accidental matches.
-
Reset request history with
http_mock.clear_requests()if you need to verify specific sequences. -
Test error scenarios thoroughly to ensure your application handles errors gracefully.
-
Add timeouts for async tests to prevent tests from hanging indefinitely.
-
Verify both requests and state changes to ensure the full flow works correctly.
-
Use schema validation for critical API endpoints to ensure your requests have the correct structure before sending them to the server.
-
Combine schema validation with custom errors to make test failures more informative.
Common Issues and Solutions
Mock not intercepting requests
- Ensure the URL pattern matches exactly what your code is requesting
- Make sure the mock is installed before making requests
- Verify the HTTP method matches (GET vs POST vs PUT)
Tests timing out
- Check for unresolved promises in your tests
- Ensure all async operations have proper error handling
- Add explicit timeouts to long-running tests
Mock state persisting between tests
- Call
http_mock.uninstall()at the end of each test - Clean up local storage and session storage
- Reset any global state in your application
Schema validation not working
- Check that the request body is valid JSON
- Ensure the schema structure matches the expected JSON structure
- Check for typos in property names or incorrect types
- Make sure schema validation is added before making the request
- Use
add_schema_validation_with_errorwith a custom error message to get more details