Oxidite Testing
Comprehensive testing utilities for the Oxidite web framework.

Overview
oxidite-testing provides a comprehensive set of utilities for testing Oxidite web applications. It includes tools for creating test requests, asserting on responses, mocking external dependencies, and setting up test environments with proper fixtures and teardown.
Installation
Add this to your Cargo.toml as a development dependency:
[dev-dependencies]
oxidite-testing = "0.1"
Features
- Test Client: Full-featured HTTP client for integration testing
- Request Builder: Fluent API for creating HTTP requests with various content types
- Response Assertions: Rich assertion methods for testing response properties
- Async Test Support: Built-in async test utilities with proper runtime setup
- Mock Services: Tools for mocking external APIs and services
- Test Fixtures: Utilities for setting up and tearing down test data
- Database Testing: Tools for testing with temporary databases
- Route Testing: Easy testing of individual routes and handlers
- Middleware Testing: Tools for testing middleware in isolation
- Integration Testing: Complete app testing with full middleware stack
Usage
Basic Integration Testing
Test your complete application with the TestClient:
use oxidite::prelude::*;
use oxidite_testing::TestClient;
async fn hello_handler(_req: OxiditeRequest) -> Result<OxiditeResponse> {
Ok(response::text("Hello, World!"))
}
#[tokio::test]
async fn test_hello_endpoint() {
let mut router = Router::new();
router.get("/", hello_handler);
let client = TestClient::new(router);
let response = client.get("/").send().await;
assert_eq!(response.status(), 200);
assert_eq!(response.text().await, "Hello, World!");
}
Request Building
Create complex test requests with the fluent API:
use oxidite_testing::TestClient;
use serde_json::json;
#[tokio::test]
async fn test_post_json() {
let client = TestClient::new(my_router());
let response = client
.post("/api/users")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token")
.json(&json!({
"name": "John Doe",
"email": "john@example.com"
}))
.send()
.await;
assert_eq!(response.status(), 201);
assert_eq!(response.json::<serde_json::Value>().await["name"], "John Doe");
}
Testing Different HTTP Methods
Test all HTTP methods with appropriate assertions:
use oxidite_testing::TestClient;
#[tokio::test]
async fn test_rest_endpoints() {
let client = TestClient::new(my_router());
let get_resp = client.get("/api/users/1").send().await;
assert_eq!(get_resp.status(), 200);
let post_resp = client
.post("/api/users")
.json(&json!({"name": "New User"}))
.send()
.await;
assert_eq!(post_resp.status(), 201);
let put_resp = client
.put("/api/users/1")
.json(&json!({"name": "Updated User"}))
.send()
.await;
assert_eq!(put_resp.status(), 200);
let del_resp = client.delete("/api/users/1").send().await;
assert_eq!(del_resp.status(), 204);
}
Response Assertions
Use rich assertion methods to verify response properties:
use oxidite_testing::TestClient;
#[tokio::test]
async fn test_response_properties() {
let client = TestClient::new(my_router());
let response = client.get("/api/users").send().await;
response.assert_status(200);
response.assert_ok();
response.assert_success();
response.assert_header("content-type", "application/json");
response.assert_has_header("x-request-id");
let body = response.text().await;
assert!(body.contains("users"));
let json_value = response.json::<serde_json::Value>().await;
assert_eq!(json_value["data"].as_array().unwrap().len(), 5);
}
Testing with State and Extractors
Test handlers that use Oxidite's extractor system:
use oxidite::prelude::*;
use oxidite_testing::TestClient;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db_connection: String, }
async fn handler_with_state(
_req: OxiditeRequest,
State(state): State<Arc<AppState>>
) -> Result<OxiditeResponse> {
Ok(response::json(serde_json::json!({
"db": state.db_connection
})))
}
#[tokio::test]
async fn test_with_state() {
let app_state = Arc::new(AppState {
db_connection: "mock_db".to_string(),
});
let mut router = Router::new();
router.get("/", handler_with_state);
let client = TestClient::with_state(router, app_state);
let response = client.get("/").send().await;
assert_eq!(response.status(), 200);
let json = response.json::<serde_json::Value>().await;
assert_eq!(json["db"], "mock_db");
}
Testing Individual Handlers
Test individual handler functions in isolation:
use oxidite::prelude::*;
use oxidite_testing::TestRequest;
async fn create_user_handler(
Json(payload): Json<serde_json::Value>
) -> Result<OxiditeResponse> {
if payload["name"].is_null() {
return Err(OxiditeError::BadRequest("Name is required".to_string()));
}
Ok(response::json(json!({
"id": 1,
"name": payload["name"].as_str().unwrap()
})))
}
#[tokio::test]
async fn test_create_user_handler() {
let req = TestRequest::post("/")
.json(&json!({"name": "John"}))
.build();
let response = create_user_handler(req.extract().await.unwrap()).await.unwrap();
assert_eq!(response.status().as_u16(), 200);
let req = TestRequest::post("/")
.json(&json!({"email": "test@example.com"}))
.build();
let result = create_user_handler(req.extract().await.unwrap()).await;
assert!(result.is_err());
match result.err().unwrap() {
OxiditeError::BadRequest(_) => {}, _ => panic!("Expected BadRequest error"),
}
}
Mocking External Services
Mock external API calls and services:
use oxidite_testing::MockServer;
#[tokio::test]
async fn test_with_external_service() {
let mock_server = MockServer::start().await;
mock_server
.mock(|when, then| {
when.path("/api/external");
then.status(200).json_body(json!({"data": "mocked"}));
})
.await;
std::env::set_var("EXTERNAL_API_URL", mock_server.url());
let client = TestClient::new(my_router());
let response = client.get("/use-external-api").send().await;
assert_eq!(response.status(), 200);
let mocks = mock_server.received_requests().await;
assert_eq!(mocks.len(), 1);
}
Database Testing
Test with temporary databases:
use oxidite_testing::TestDatabase;
#[tokio::test]
async fn test_with_database() {
let test_db = TestDatabase::new("sqlite").await;
let pool = test_db.pool();
let client = TestClient::with_state(
my_router_with_db(pool.clone()),
Arc::new(AppState { db_pool: pool })
);
let response = client.get("/api/users").send().await;
assert_eq!(response.status(), 200);
}
Testing Middleware
Test middleware components in isolation:
use tower::{Service, ServiceExt, ServiceBuilder};
use http::{Request, Response, StatusCode};
use oxidite_testing::TestRequest;
use oxidite_middleware::LoggerLayer;
#[tokio::test]
async fn test_logging_middleware() {
let mut service = ServiceBuilder::new()
.layer(LoggerLayer::new())
.service_fn(echo_service);
let request = Request::builder()
.uri("/")
.body(hyper::Body::empty())
.unwrap();
let response = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
async fn echo_service(req: Request<hyper::Body>) -> Result<Response<hyper::Body>, std::convert::Infallible> {
Ok(Response::new(req.into_body()))
}
Running Tests
Run your tests with cargo:
cargo test
RUST_LOG=debug cargo test
cargo test test_hello_endpoint
cargo test -- --test-threads=4
cargo test -- --test-threads=1
Test Organization
Organize your tests effectively:
#[cfg(test)]
mod tests {
use super::*;
use oxidite_testing::TestClient;
#[tokio::test]
async fn unit_tests() {
}
mod integration {
use super::*;
#[tokio::test]
async fn api_tests() {
}
#[tokio::test]
async fn auth_tests() {
}
}
mod performance {
use super::*;
#[tokio::test]
async fn load_tests() {
}
}
}
Best Practices
- Use descriptive test names that explain what is being tested
- Test both success and failure scenarios
- Use appropriate assertion libraries for complex comparisons
- Mock external dependencies to isolate your code
- Test edge cases and error conditions
- Use test fixtures to set up common test data
- Keep tests fast and deterministic
- Organize tests in logical groups
License
MIT