Skip to main content

Module test_assertions

Module test_assertions 

Source
Expand description

Django-shape test assertion helpers — assert_contains / assert_redirects / assert_status / assert_messages on axum Response objects. Issue #40. Django-shape assertion helpers for axum response objects. Issue #40.

Quick assertions every Django test suite uses, ported to axum’s Response shape so test code reads tighter:

use rustango::test_assertions::{assert_contains, assert_redirects, assert_status};
use tower::ServiceExt;

#[tokio::test]
async fn home_renders_greeting() {
    let app = make_app();
    let res = app.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()).await.unwrap();
    assert_status(&res, 200);
    assert_contains(res, "Hello, world!").await;
}

#[tokio::test]
async fn login_redirects_anonymous() {
    let res = app.oneshot(req("/profile")).await.unwrap();
    assert_redirects(&res, "/login?next=%2Fprofile");
}

All helpers panic! on mismatch — cargo test reports them as failures with descriptive messages. They don’t return Result so tests stay readable (no ? clutter for assertions).

§Implemented

  • [assert_status] — exact-status match against a u16.
  • [assert_status_in] — status is one of an allowed set.
  • [assert_status_2xx] — status is in the 200–299 range.
  • [assert_status_4xx] — status is in the 400–499 range.
  • [assert_status_5xx] — status is in the 500–599 range.
  • [assert_contains] — response body contains a UTF-8 substring.
  • [assert_not_contains] — body does NOT contain a substring.
  • [assert_contains_count] — body contains fragment N times (Django’s assertContains(..., count=N)).
  • [assert_redirects] — 3xx status + Location header equality.
  • [assert_header] — exact header value match.
  • [assert_content_type] — sugar for assert_header("content-type", ...).
  • [assert_json_eq] — body parses as JSON and equals expected (Django’s assertJSONEqual).
  • [assert_json_not_eq] — body parses as JSON and DIFFERS from expected (Django’s assertJSONNotEqual).
  • [assert_redirect_chain] — inspect the chain produced by crate::test_client::TestClient::get_following_redirects and assert it ends at a given (path, status). Django’s assertRedirects(..., fetch_redirect_response=True).
  • [assert_messages] — read + assert on the flash-messages cookie from crate::messages. Gated on template_views so consumers that use messages get the helper for free.
  • [assert_cookie_set] — assert a Set-Cookie header for the given cookie name was emitted, with optional exact-value match.
  • [assert_cookie_not_set] — inverse: assert no Set-Cookie for the given name was emitted.

§Out of scope (queued as follow-ups)

Django’s full assertion surface includes:

  • assertTemplateUsed — needs a Tera render-tracking hook; would require wrapping the Tera instance with an instrumented variant.
  • assertNumQueries — needs a query-counting probe on Pool. Doable with a wrapping executor but adds complexity.
  • assertFormError(response, field, message) — needs Form errors in the rendered template context, which is template-shape specific. Right now FormView stamps errors: HashMap into context — a helper could inspect that map but only for views that use the canonical key.

The helpers here are the high-leverage subset that needs nothing beyond axum::Response plus the existing test_client redirect follower.

Re-exports§

pub use query_counter::assert_num_queries;
pub use query_counter::QueryCounter;

Modules§

query_counter
Django-shape assertNumQueries — count SQL queries executed inside a scoped async block, then assert the count matches an expectation. Django-parity #431.

Functions§

assert_contains
Assert the response body contains fragment as a UTF-8 substring. Consumes the body, so the response is moved in.
assert_contains_count
Assert the response body contains fragment exactly count times. Django’s assertContains(..., count=N). count = 0 asserts absence — same as assert_not_contains.
assert_content_type
Assert the response Content-Type header equals expected. Sugar for assert_header with name = "content-type".
assert_cookie_not_set
Inverse of assert_cookie_set — panic if a Set-Cookie for name IS present. Use to verify that a handler did NOT set a cookie under specific conditions (logged-out request shouldn’t touch the session cookie, etc.).
assert_cookie_set
Assert that the response emitted a Set-Cookie header for the given cookie name. If expected_value is Some, also assert the cookie’s value (the portion BEFORE the first ; — i.e. the name=value segment without Path / HttpOnly / etc.) equals that value byte-for-byte. Returns the matched Set-Cookie header value(s).
assert_header
Assert that response header name equals value exactly. Matches the header name case-insensitively (per RFC 7230), and the value byte-for-byte after UTF-8 decode.
assert_json_eq
Assert the response body, parsed as JSON, equals expected. Django’s assertJSONEqual. Order-insensitive for object keys (JSON Value equality is structural).
assert_json_not_eq
Inverse of assert_json_eq — Django’s assertJSONNotEqual. Asserts the parsed JSON body differs from unexpected.
assert_messages
Drain the Set-Cookie value(s) for the messages framework cookie from res and assert the staged messages match expected — list of (level_str, body) pairs.
assert_not_contains
Inverse of assert_contains — panics when the fragment IS found. Useful for “the deleted post shouldn’t appear in the list” style assertions.
assert_redirect_chain
Assert the redirect chain produced by crate::test_client::TestClient::get_following_redirects ends at final_path with final_status. Django’s assertRedirects with fetch_redirect_response=True.
assert_redirects
Assert the response is a 3xx redirect with Location exactly equal to target. Catches both the status check and the URL check in one assertion — Django’s assertRedirects shape.
assert_status
Assert the response status equals expected. Panics on mismatch with the actual status in the message.
assert_status_2xx
Assert the response status is in the 2xx range (success). Sugar for the very common “any success” check.
assert_status_4xx
Assert the response status is in the 4xx range (client error).
assert_status_5xx
Assert the response status is in the 5xx range (server error). Useful for negative tests of error pages / handlers that must surface internal failures rather than degrade silently.
assert_status_in
Assert the response status is one of allowed. Useful when a handler can legitimately return either of several success codes — e.g. POST /items might return 200 (idempotent touch) OR 201 (new resource).