| Crates.io | oxidite-testing |
| lib.rs | oxidite-testing |
| version | 2.0.1 |
| created_at | 2025-12-07 14:34:07.761324+00 |
| updated_at | 2026-01-25 01:55:16.707434+00 |
| description | Testing utilities for the Oxidite v2 web framework |
| homepage | |
| repository | https://github.com/meshackbahati/rust-oxidite |
| max_upload_size | |
| id | 1971695 |
| size | 97,567 |
Comprehensive testing utilities for the Oxidite web framework.
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.
Add this to your Cargo.toml as a development dependency:
[dev-dependencies]
oxidite-testing = "0.1"
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!");
}
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");
}
Test all HTTP methods with appropriate assertions:
use oxidite_testing::TestClient;
#[tokio::test]
async fn test_rest_endpoints() {
let client = TestClient::new(my_router());
// GET request
let get_resp = client.get("/api/users/1").send().await;
assert_eq!(get_resp.status(), 200);
// POST request
let post_resp = client
.post("/api/users")
.json(&json!({"name": "New User"}))
.send()
.await;
assert_eq!(post_resp.status(), 201);
// PUT request
let put_resp = client
.put("/api/users/1")
.json(&json!({"name": "Updated User"}))
.send()
.await;
assert_eq!(put_resp.status(), 200);
// DELETE request
let del_resp = client.delete("/api/users/1").send().await;
assert_eq!(del_resp.status(), 204);
}
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;
// Status assertions
response.assert_status(200);
response.assert_ok();
response.assert_success();
// Header assertions
response.assert_header("content-type", "application/json");
response.assert_has_header("x-request-id");
// Body assertions
let body = response.text().await;
assert!(body.contains("users"));
// JSON assertions
let json_value = response.json::<serde_json::Value>().await;
assert_eq!(json_value["data"].as_array().unwrap().len(), 5);
}
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, // Mock database connection
}
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");
}
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() {
// Test successful creation
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);
// Test validation error
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(_) => {}, // Expected
_ => panic!("Expected BadRequest error"),
}
}
Mock external API calls and services:
use oxidite_testing::MockServer;
#[tokio::test]
async fn test_with_external_service() {
// Start a mock server
let mock_server = MockServer::start().await;
// Create a mock endpoint
mock_server
.mock(|when, then| {
when.path("/api/external");
then.status(200).json_body(json!({"data": "mocked"}));
})
.await;
// Configure your app to use the mock server URL
std::env::set_var("EXTERNAL_API_URL", mock_server.url());
// Test your application
let client = TestClient::new(my_router());
let response = client.get("/use-external-api").send().await;
assert_eq!(response.status(), 200);
// Verify the mock was called
let mocks = mock_server.received_requests().await;
assert_eq!(mocks.len(), 1);
}
Test with temporary databases:
use oxidite_testing::TestDatabase;
#[tokio::test]
async fn test_with_database() {
// Create a temporary database
let test_db = TestDatabase::new("sqlite").await;
let pool = test_db.pool();
// Insert test data
// ... insert test records ...
// Test your application
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);
// Database is automatically cleaned up
}
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()))
}
Run your tests with cargo:
# Run all tests
cargo test
# Run tests with logging
RUST_LOG=debug cargo test
# Run specific test
cargo test test_hello_endpoint
# Run tests in parallel (default)
cargo test -- --test-threads=4
# Run tests sequentially
cargo test -- --test-threads=1
Organize your tests effectively:
// src/lib.rs or src/main.rs
#[cfg(test)]
mod tests {
use super::*;
use oxidite_testing::TestClient;
#[tokio::test]
async fn unit_tests() {
// Test individual functions
}
mod integration {
use super::*;
#[tokio::test]
async fn api_tests() {
// Test API endpoints
}
#[tokio::test]
async fn auth_tests() {
// Test authentication
}
}
mod performance {
use super::*;
#[tokio::test]
async fn load_tests() {
// Performance/load tests
}
}
}
MIT