Testing
Testing
Blixt provides a testing module with an HTTP test client, database pool
helpers, and a re-export of the fake crate for generating test data.
Setup
Add the test-helpers feature to your dev-dependencies:
# Cargo.toml
[dev-dependencies]
blixt = { version = "0.5", features = ["test-helpers"] }
tokio = { version = "1", features = ["full"] }
Import from blixt::testing:
use blixt::testing::{TestClient, TestResponse, test_config};
HTTP integration tests
TestClient wraps an Axum Router and provides a fluent API for sending
requests and asserting on responses:
use axum::routing::get;
use axum::http::StatusCode;
use blixt::prelude::*;
use blixt::testing::TestClient;
fn app() -> Router {
Router::new().route("/health", get(|| async { "ok" }))
}
#[tokio::test]
async fn health_returns_200() {
let client = TestClient::new(app());
let resp = client.get("/health").await;
resp.assert_status(StatusCode::OK);
assert_eq!(resp.text().await, "ok");
}
Request methods
client.get("/path").await // GET, returns TestResponse
client.post("/path").json(&body).send().await // POST with JSON body
client.put("/path").json(&body).send().await // PUT with JSON body
client.patch("/path").json(&body).send().await // PATCH with JSON body
client.delete("/path").send().await // DELETE
client.post("/path").header("x-key", "val").send().await // custom header
client.post("/path").signals(&json!({})).send().await // Datastar signals
Response assertions
let resp = client.get("/api/users").await;
resp.assert_status(StatusCode::OK); // panic if status doesn't match
resp.assert_header("content-type", "application/json");
let body = resp.text().await; // read body as string
let user: User = resp.json().await; // deserialize JSON
let status = resp.status(); // get StatusCode
let ct = resp.header("content-type"); // get header value
assert_status and assert_header return self, so you can chain:
client.get("/api/me").await
.assert_status(StatusCode::OK)
.assert_header("content-type", "application/json");
Database tests
TestPool connects to a test database and runs migrations once per process.
Subsequent calls reuse the same pool.
Set TEST_DATABASE_URL in your environment or .env:
TEST_DATABASE_URL=postgres://user:pass@localhost/myapp_test
use blixt::testing::TestPool;
use blixt::prelude::*;
#[tokio::test]
async fn creates_user() {
blixt::require_test_db!();
let pool = TestPool::new().await;
Insert::into("users")
.set("email", "test@example.com")
.set("name", "Test User")
.returning::<User>(&["id", "email", "name"])
.execute(&pool)
.await
.unwrap();
}
require_test_db!() skips the test if TEST_DATABASE_URL is not set, so
your test suite stays green in environments without a database.
TestPool implements Deref<Target = DbPool>, so you pass &pool directly
to query builder methods.
Fake data
The fake crate is re-exported at blixt::testing::fake. Use it to generate
realistic test data:
use blixt::testing::fake::{Fake, faker::internet::en::*, faker::name::en::*};
fn random_email() -> String {
SafeEmail().fake()
}
fn random_name() -> String {
Name().fake()
}
Factory pattern
Build test factories using the query builder's .set() method with overrides:
use blixt::prelude::*;
use blixt::testing::fake::{Fake, faker::internet::en::*, faker::name::en::*};
fn user_insert() -> Insert {
Insert::into("users")
.set("email", SafeEmail().fake::<String>())
.set("name", Name().fake::<String>())
.set("role", "user")
}
#[tokio::test]
async fn admin_can_delete() {
blixt::require_test_db!();
let pool = TestPool::new().await;
let admin = user_insert()
.set("role", "admin")
.returning::<User>(&["id", "email", "name", "role"])
.execute(&pool)
.await
.unwrap();
assert_eq!(admin.role, "admin");
}
Environment variable helpers
with_env_vars sets variables for the duration of a closure, then restores
the original values. It serializes via a mutex to avoid races between tests:
use blixt::testing::with_env_vars;
#[test]
fn config_reads_custom_port() {
with_env_vars(&[("PORT", Some("8080"))], || {
let config = Config::from_env().unwrap();
assert_eq!(config.port, 8080);
});
}
Test configuration
test_config() returns a Config set to test mode on port 0 (OS-assigned),
useful for building routers in tests without needing environment variables:
use blixt::testing::test_config;
let config = test_config();
let app = App::new(config).router(routes).build_router();