Skip to content

Add integration tests with mock HTTP server #19

@cbaugus

Description

@cbaugus

Summary

The load testing tool currently has no integration tests that verify end-to-end functionality. We need tests that spin up a mock HTTP server and verify that the load tester correctly generates requests, tracks metrics, and handles various response scenarios.

Proposed Approach

Use wiremock or httpmock crate to create a mock HTTP server, then run the load tester against it and verify behavior.

Test Scenarios Required

Basic Functionality

  • GET requests are made to the correct URL
  • POST requests include the correct body
  • Custom headers are sent correctly
  • Correct number of concurrent connections established

Load Models

  • Concurrent model makes unlimited requests
  • RPS model rate-limits to target rate (within tolerance)
  • RampRps model increases/decreases rate correctly
  • DailyTraffic model follows phase patterns

Metrics Verification

  • requests_total increments correctly
  • requests_status_codes_total tracks by status code
  • request_duration_seconds histogram populated
  • concurrent_requests gauge reflects active requests
  • Metrics endpoint (port 9090) returns valid Prometheus format

Error Handling

  • Connection refused handled gracefully
  • Timeout handled correctly
  • 4xx responses counted in metrics
  • 5xx responses counted in metrics
  • TLS errors handled appropriately

Response Scenarios

  • 200 OK responses tracked
  • 201 Created responses tracked
  • 400 Bad Request responses tracked
  • 404 Not Found responses tracked
  • 500 Internal Server Error responses tracked
  • Slow responses (latency injection) tracked accurately

Test Structure

tests/
├── integration/
│   ├── mod.rs
│   ├── basic_requests.rs
│   ├── load_models.rs
│   ├── metrics.rs
│   └── error_handling.rs

Example Test

use wiremock::{MockServer, Mock, ResponseTemplate};
use wiremock::matchers::{method, path};

#[tokio::test]
async fn test_get_requests_sent_correctly() {
    // Start mock server
    let mock_server = MockServer::start().await;
    
    Mock::given(method("GET"))
        .and(path("/test"))
        .respond_with(ResponseTemplate::new(200))
        .expect(10)  // Expect exactly 10 requests
        .mount(&mock_server)
        .await;

    // Run load tester with short duration
    let config = TestConfig {
        target_url: format!("{}/test", mock_server.uri()),
        max_concurrent: 2,
        test_duration: Duration::from_secs(1),
        load_model: LoadModel::Concurrent,
    };
    
    run_load_test(config).await;
    
    // Verify mock received expected requests
    // (wiremock will panic if expectations not met)
}

#[tokio::test]
async fn test_rps_rate_limiting() {
    let mock_server = MockServer::start().await;
    let request_count = Arc::new(AtomicU32::new(0));
    let counter = request_count.clone();
    
    Mock::given(method("GET"))
        .respond_with(move |_| {
            counter.fetch_add(1, Ordering::SeqCst);
            ResponseTemplate::new(200)
        })
        .mount(&mock_server)
        .await;

    let config = TestConfig {
        target_url: mock_server.uri(),
        load_model: LoadModel::Rps { target_rps: 10.0 },
        test_duration: Duration::from_secs(5),
        ..Default::default()
    };
    
    run_load_test(config).await;
    
    let total_requests = request_count.load(Ordering::SeqCst);
    // Should be approximately 50 requests (10 RPS * 5 seconds)
    // Allow 20% tolerance for timing variations
    assert!(total_requests >= 40 && total_requests <= 60,
        "Expected ~50 requests, got {}", total_requests);
}

#[tokio::test]
async fn test_metrics_endpoint_returns_prometheus_format() {
    let mock_server = MockServer::start().await;
    Mock::given(method("GET"))
        .respond_with(ResponseTemplate::new(200))
        .mount(&mock_server)
        .await;

    // Start load tester in background
    let handle = tokio::spawn(async move {
        run_load_test(config).await;
    });
    
    // Give it time to start metrics server
    tokio::time::sleep(Duration::from_millis(500)).await;
    
    // Fetch metrics
    let metrics = reqwest::get("http://localhost:9090/metrics")
        .await.unwrap()
        .text()
        .await.unwrap();
    
    assert!(metrics.contains("requests_total"));
    assert!(metrics.contains("request_duration_seconds"));
    
    handle.abort();
}

Acceptance Criteria

  • Integration test directory structure created
  • Mock server setup utility created
  • Basic GET/POST request tests pass
  • At least one test per load model
  • Metrics endpoint tested
  • Error scenarios tested
  • Tests run in CI pipeline
  • Tests complete in reasonable time (<30 seconds total)

Dependencies

Add to Cargo.toml (dev-dependencies):

[dev-dependencies]
wiremock = "0.5"
tokio-test = "0.4"

Priority

High - Integration tests are essential for confidence in the tool's correctness before using it for actual load testing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions