9.6 KiB
description
| description |
|---|
| Create integration tests in the tests/ directory |
You are helping create integration tests for a Rust project in the tests/ directory.
Your Task
Set up integration test infrastructure and create comprehensive integration tests that test the public API.
Steps
-
Ask for Test Details
Ask the user:
- What feature/module to integration test?
- Do you need database/HTTP mocking?
- Should we use testcontainers for real databases?
-
Create Integration Test File
Create a new file in
tests/directory:tests/ ├── [feature]_integration.rs └── common/ └── mod.rs # Shared test utilities -
Set Up Common Utilities
Create
tests/common/mod.rs:// tests/common/mod.rs #![allow(dead_code)] use my_crate::*; pub fn setup() { // Common setup logic } pub fn teardown() { // Common cleanup logic } -
Generate Integration Test
Create the integration test file:
// tests/[feature]_integration.rs use my_crate::*; mod common; #[test] fn test_[feature]_complete_workflow() { common::setup(); // Arrange let app = create_test_application(); // Act let result = app.execute_workflow(); // Assert assert!(result.is_ok()); common::teardown(); } fn create_test_application() -> Application { // Setup test instance with test configuration Application::new(test_config()) } fn test_config() -> Config { Config { database_url: "postgres://localhost/test_db".to_string(), // ... other test config } } -
Add Async Integration Tests
For async applications:
#[tokio::test] async fn test_async_integration() { let app = setup_test_app().await; let result = app.process_request(test_request()).await; assert!(result.is_ok()); } async fn setup_test_app() -> Application { Application::new(test_config()).await.unwrap() } -
Add Database Integration Tests
Using testcontainers:
use testcontainers::{clients, images::postgres::Postgres}; use sqlx::PgPool; #[tokio::test] async fn test_with_real_database() { let docker = clients::Cli::default(); let postgres = docker.run(Postgres::default()); let connection_string = format!( "postgres://postgres@localhost:{}/postgres", postgres.get_host_port_ipv4(5432) ); let pool = PgPool::connect(&connection_string).await.unwrap(); // Run migrations sqlx::migrate!("./migrations") .run(&pool) .await .unwrap(); // Now test with real database let repo = PostgresRepository::new(pool); let service = MyService::new(repo); let result = service.create_item("test").await; assert!(result.is_ok()); }Or create a helper in common:
// tests/common/mod.rs use sqlx::PgPool; use testcontainers::*; pub async fn setup_test_database() -> PgPool { let docker = clients::Cli::default(); let postgres = docker.run(images::postgres::Postgres::default()); let url = format!( "postgres://postgres@localhost:{}/postgres", postgres.get_host_port_ipv4(5432) ); let pool = PgPool::connect(&url).await.unwrap(); sqlx::migrate!().run(&pool).await.unwrap(); pool } // tests/database_integration.rs mod common; #[tokio::test] async fn test_repository() { let pool = common::setup_test_database().await; let repo = MyRepository::new(pool); // Test repository operations } -
Add HTTP API Integration Tests
For testing HTTP APIs:
use axum::body::Body; use axum::http::{Request, StatusCode}; use tower::ServiceExt; #[tokio::test] async fn test_api_endpoint() { let app = create_test_app(); let response = app .oneshot( Request::builder() .uri("/api/users") .method("GET") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); let body = hyper::body::to_bytes(response.into_body()) .await .unwrap(); let users: Vec<User> = serde_json::from_slice(&body).unwrap(); assert!(!users.is_empty()); }Or using reqwest for full HTTP testing:
#[tokio::test] async fn test_api_full_request() { let server = spawn_test_server().await; let client = reqwest::Client::new(); let response = client .get(format!("{}/api/users", server.url())) .send() .await .unwrap(); assert_eq!(response.status(), 200); let users: Vec<User> = response.json().await.unwrap(); assert!(!users.is_empty()); } async fn spawn_test_server() -> TestServer { // Start server on random port for testing TestServer::spawn().await } -
Add HTTP Mocking with wiremock
For testing external API calls:
use wiremock::{MockServer, Mock, ResponseTemplate}; use wiremock::matchers::{method, path}; #[tokio::test] async fn test_external_api_integration() { let mock_server = MockServer::start().await; Mock::given(method("GET")) .and(path("/external/api")) .respond_with(ResponseTemplate::new(200).set_body_json( serde_json::json!({ "status": "ok", "data": "test" }) )) .mount(&mock_server) .await; let client = ExternalApiClient::new(&mock_server.uri()); let result = client.fetch_data().await.unwrap(); assert_eq!(result.status, "ok"); } -
Add Multi-Step Workflow Tests
Test complete user workflows:
#[tokio::test] async fn test_complete_user_workflow() { let app = setup_test_app().await; // Step 1: Create user let user_id = app .create_user("test@example.com") .await .unwrap(); // Step 2: Retrieve user let user = app .get_user(&user_id) .await .unwrap(); assert_eq!(user.email, "test@example.com"); // Step 3: Update user app.update_user(&user_id, "new@example.com") .await .unwrap(); // Step 4: Verify update let updated = app .get_user(&user_id) .await .unwrap(); assert_eq!(updated.email, "new@example.com"); // Step 5: Delete user app.delete_user(&user_id) .await .unwrap(); // Step 6: Verify deletion let result = app.get_user(&user_id).await; assert!(result.is_err()); } -
Update Dev Dependencies
Ensure required dependencies are in Cargo.toml:
[dev-dependencies] tokio = { version = "1", features = ["full", "test-util"] } testcontainers = "0.15" wiremock = "0.6" reqwest = { version = "0.11", features = ["json"] } -
Provide Summary
✅ Integration tests created successfully! ## Files Created: - `tests/[feature]_integration.rs` - Main integration test file - `tests/common/mod.rs` - Shared test utilities ## Tests Added: - test_[feature]_complete_workflow - test_database_operations - test_api_endpoints - test_external_api_integration ## Infrastructure: - Database test setup with testcontainers - HTTP mocking with wiremock - Test configuration helpers ## Dependencies Added: [List of dev dependencies] ## Running Integration Tests: ```bash # Run all integration tests cargo test --test [feature]_integration # Run specific test cargo test test_complete_workflow # Run with output cargo test --test [feature]_integration -- --nocapture # Run all integration tests cargo test --testsNext Steps:
- Review and customize test cases
- Add more workflow scenarios
- Run tests:
cargo test --tests - Check if you need more test infrastructure
Integration Test Patterns
Setup/Teardown Pattern
struct TestContext {
pool: PgPool,
// Other resources
}
impl TestContext {
async fn new() -> Self {
// Setup
Self {
pool: setup_database().await,
}
}
}
impl Drop for TestContext {
fn drop(&mut self) {
// Cleanup
}
}
#[tokio::test]
async fn test_with_context() {
let ctx = TestContext::new().await;
// Use ctx.pool
}
Parallel Test Isolation
#[tokio::test]
async fn test_isolated_1() {
let db = create_unique_test_db("test1").await;
// Each test gets its own database
}
#[tokio::test]
async fn test_isolated_2() {
let db = create_unique_test_db("test2").await;
// Runs in parallel without interference
}
Important Notes
- Integration tests are in separate crates from src/
- Each file in tests/ is a separate binary
- Common code goes in tests/common/ (not tests/common.rs)
- Use real implementations when possible
- Mock external services
- Clean up resources after tests
- Tests should be independent and runnable in any order
After Completion
Ask the user:
- Did the integration tests pass?
- Do you need more test scenarios?
- Should we add performance/load tests?
- Do you want to set up CI/CD for these tests?