Skip to main content

TestClient

Struct TestClient 

Source
pub struct TestClient { /* private fields */ }
Expand description

Fluent HTTP client for integration tests.

Analogous to Spring Boot’s MockMvc or Django’s Client. Fires requests through the full Axum middleware pipeline using tower::ServiceExt::oneshot() – no TCP listener required.

Created by TestApp::build().

§Examples

let client = TestApp::new().routes(routes![handler]).build();

// GET request
client.get("/path").send().await.assert_ok();

// POST with JSON body
client.post("/items")
    .json(&serde_json::json!({"name": "foo"}))
    .send().await
    .assert_status(201);

// PUT with header
client.put("/items/1")
    .header("authorization", "Bearer token")
    .json(&serde_json::json!({"name": "bar"}))
    .send().await
    .assert_ok();

Implementations§

Source§

impl TestClient

Source

pub const fn state(&self) -> &AppState

Returns a reference to the AppState wired into this test app’s router.

Source

pub fn published_events<E: Event>(&self) -> Vec<E>

Every recorded publication of event type E, deserialized.

Events are recorded synchronously at publish time, so this works whether or not the listeners (sync or durable) have run.

Source

pub fn assert_event_published<E: Event>(&self)

Assert that at least one event of type E was published during the test.

§Panics

Panics if no event of type E was recorded.

Source

pub fn advance_clock(&self, duration: Duration)

Step the test clock forward by duration.

Only effective when the app was configured with a crate::time::TickingClock via TestApp::with_clock. Calling this with a crate::time::FixedClock or without any custom clock is a safe no-op — time stays where it is.

This method only affects the wall-clock time reported by the crate::time::Clock extractor. Tokio’s runtime timer (used by tokio::time::sleep, tokio::time::Instant, etc.) is not affected.

use autumn_web::test::TestApp;
use autumn_web::time::TickingClock;
use chrono::{TimeZone, Utc};
use std::time::Duration;

let clock = TickingClock::starting_at(Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap());
let client = TestApp::new().with_clock(clock).build();

client.advance_clock(Duration::from_secs(86400)); // advance 1 day
Source

pub fn into_router(self) -> Router

Unwrap the underlying axum::Router out of the TestClient.

Source

pub const fn probes(&self) -> &ProbeState

Return the crate::probe::ProbeState wired into this test app’s router.

Use this to drive readiness/liveness transitions in integration tests and verify the HTTP probe endpoints reflect state changes.

Source

pub fn sent_mail(&self) -> Vec<SentMail>

Returns all emails sent during this test, in the order they were sent.

The built-in recorder is installed automatically — no .with_mail_interceptor(…) call is required.

§Example
client.post("/signup").json(&body).send().await.assert_ok();
let mail = &client.sent_mail()[0];
assert_eq!(mail.subject, "Welcome!");
Source

pub fn assert_email_count(&self, n: usize) -> &Self

Asserts that exactly n emails were sent, panicking with a list of what was actually sent on failure.

Returns &self for chaining.

§Panics

Panics when the count does not match.

Source

pub fn assert_no_email_sent(&self) -> &Self

Asserts that no emails were sent.

Returns &self for chaining.

§Panics

Panics when any emails were sent.

Source

pub fn assert_email_sent(&self, predicate: impl Fn(&SentMail) -> bool) -> &Self

Asserts that at least one sent email satisfies predicate, panicking with a list of what was actually sent on failure.

Returns &self for chaining.

§Panics

Panics when no sent email matches.

§Example
client
    .assert_email_sent(|m| m.to.iter().any(|a| a == "alice@example.com"))
    .assert_email_sent(|m| m.subject == "Welcome!");
Source

pub fn broadcasts(&self) -> Vec<RecordedBroadcast>

Every recorded channel publication, in publish order.

Requires opting in with TestApp::record_broadcasts. Captures both raw publish text and publish_html HTML/OOB payloads.

§Panics

Panics if TestApp::record_broadcasts was not called.

Source

pub fn broadcasts_on(&self, topic: &str) -> Vec<RecordedBroadcast>

Recorded publications on topic, in publish order.

§Panics

Panics if TestApp::record_broadcasts was not called.

Source

pub fn assert_broadcast( &self, topic: &str, predicate: impl Fn(&RecordedBroadcast) -> bool, ) -> &Self

Asserts that at least one publication on topic satisfies predicate.

Returns &Self for chaining.

§Panics

Panics when no matching publication is found, dumping what was published to topic and nearby topics.

Source

pub fn assert_broadcast_count(&self, topic: &str, n: usize) -> &Self

Asserts that exactly n publications were made to topic.

Returns &Self for chaining.

§Panics

Panics when the count does not match, dumping what was published to topic and nearby topics.

Source

pub fn assert_no_broadcasts(&self, topic: &str) -> &Self

Asserts that nothing was published to topic.

Returns &Self for chaining.

§Panics

Panics when any publication was made to topic, dumping what was published to topic and nearby topics.

Source

pub fn enqueued_jobs(&self) -> Vec<RecordedJob>

Every background-job enqueue captured by the built-in recorder, in the order they were enqueued (across enqueue, enqueue_after_commit, and enqueue_in_tx).

The recorder is always on for TestApp::build clients — no opt-in.

§Panics

Panics if called on a TestClient built via TestApp::from_router, which bypasses recorder wiring.

Source

pub fn assert_job_enqueued(&self, name: &str) -> &Self

Assert at least one job with the given registered name was enqueued.

§Panics

Panics, listing every job that was enqueued, if no enqueue with that name was captured.

Source

pub fn assert_job_enqueued_with(&self, name: &str, payload: Value) -> &Self

Assert at least one job was enqueued with both the given registered name and an exactly-equal JSON payload.

§Panics

Panics, listing every job that was enqueued, if no enqueue matched both the name and payload.

Source

pub fn assert_no_jobs_enqueued(&self) -> &Self

Assert no jobs were enqueued at all.

§Panics

Panics, listing every captured enqueue, if any job was enqueued.

Source

pub async fn perform_enqueued_jobs(&self) -> PerformedJobs

Drain every captured job and dispatch it through its registered handler, awaiting each in enqueue order, so a test can assert the resulting side effects synchronously.

Each captured payload is handed to the same handler the runtime would invoke, so the real deserialization path runs: a payload that cannot be deserialized into the job’s args surfaces as a per-job failure (not a silent miss). The queue is emptied — a second call performs nothing until more jobs are enqueued.

Returns a PerformedJobs report carrying each job’s (name, result); per-job handler errors (and captured jobs with no registered handler) are surfaced there rather than swallowed. See PerformedJobs::assert_all_succeeded.

§Note

TestApp::build starts the in-process job worker by default, and that worker also drains and runs the same enqueued jobs. Calling this method therefore executes a job’s side effect an additional time, on top of the worker’s own run. It is primarily for asserting that a job runs to completion — surfacing handler/deserialization errors synchronously — not for counting side effects. Any assertion on a side effect’s count must account for the worker’s run as well (as the job-recorder integration tests do: they settle the worker’s run first, then attribute the next increment to this call).

The helper invokes each job’s registered handler directly and does not run it through a user-installed JobInterceptor::intercept_execute, so execution-interceptor effects (context injection, metrics, error injection) are exercised by the in-process worker path, not by this helper.

§Panics

Panics if called on a TestClient built via TestApp::from_router.

Source

pub fn get(&self, uri: &str) -> RequestBuilder

Start building a GET request.

Source

pub fn post(&self, uri: &str) -> RequestBuilder

Start building a POST request.

Source

pub fn put(&self, uri: &str) -> RequestBuilder

Start building a PUT request.

Source

pub fn delete(&self, uri: &str) -> RequestBuilder

Start building a DELETE request.

Source

pub fn patch(&self, uri: &str) -> RequestBuilder

Start building a PATCH request.

Source

pub fn options(&self, uri: &str) -> RequestBuilder

Start building an OPTIONS request (e.g. a CORS preflight).

Source

pub async fn acting_as(&self, user_id: impl Display) -> &Self

Establish an authenticated session for user_id without calling the login endpoint, then return &Self for chaining.

Mints a fresh session containing the app’s configured auth.session_key (default "user_id") set to user_id, saves it to the session store the router reads, and seeds the cookie jar with the session cookie. A subsequent request to a #[secured] / Auth-gated route then extracts the same identity a real login would produce.

This sets identity only — authorization still runs. A user acted-as here who lacks a required role or scope is still denied.

Analogous to Laravel’s actingAs, Rails’ sign_in, Django’s force_login, and Phoenix’s log_in_user.

§Panics

Panics if the client has no handle to a session store — i.e. it was built via TestApp::from_router, or configured with a non-memory session backend. Use TestApp::build with the default (memory) session backend for acting_as support.

§Examples
let client = TestApp::new().routes(routes![dashboard]).build();
client.acting_as(42).await;
client.get("/dashboard").send().await.assert_ok();
Source

pub async fn login_as(&self, user_id: impl Display) -> &Self

Alias for acting_as.

Provided for readers coming from frameworks whose helper is spelled login_as / sign_in.

§Panics

See acting_as.

Source

pub fn log_out(&self) -> &Self

Clear the session cookie from the jar, reverting the client to an unauthenticated state, then return &Self for chaining.

After log_out, a request to a secured route returns its unauthenticated status (401 / redirect) again. The corresponding server-side session (if any) is left to expire naturally.

Analogous to Laravel’s Auth::logout, Rails’ sign_out, and Django’s logout.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> AggregateExpressionMethods for T

Source§

fn aggregate_distinct(self) -> Self::Output
where Self: DistinctDsl,

DISTINCT modifier for aggregate functions Read more
Source§

fn aggregate_all(self) -> Self::Output
where Self: AllDsl,

ALL modifier for aggregate functions Read more
Source§

fn aggregate_filter<P>(self, f: P) -> Self::Output
where P: AsExpression<Bool>, Self: FilterDsl<<P as AsExpression<Bool>>::Expression>,

Add an aggregate function filter Read more
Source§

fn aggregate_order<O>(self, o: O) -> Self::Output
where Self: OrderAggregateDsl<O>,

Add an aggregate function order Read more
Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AutumnDependents for T
where T: ?Sized,

Source§

fn dependents() -> &'static [RuntimeDependentSpec]

The model’s dependent-cascade specs, in declaration order. Defaults to none; #[model] overrides via an inherent shadow when dependents exist.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Sync + Send>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoSql for T

Source§

fn into_sql<T>(self) -> Self::Expression

Convert self to an expression for Diesel’s query builder. Read more
Source§

fn as_sql<'a, T>(&'a self) -> <&'a Self as AsExpression<T>>::Expression
where &'a Self: AsExpression<T>, T: SqlType + TypedExpressionType,

Convert &self to an expression for Diesel’s query builder. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, Conn> RunQueryDsl<Conn> for T

Source§

fn execute<'conn, 'query>( self, conn: &'conn mut Conn, ) -> <Conn as AsyncConnectionCore>::ExecuteFuture<'conn, 'query>
where Conn: AsyncConnectionCore + Send, Self: ExecuteDsl<Conn> + 'query,

Executes the given command, returning the number of rows affected. Read more
Source§

fn load<'query, 'conn, U>( self, conn: &'conn mut Conn, ) -> AndThen<Self::LoadFuture<'conn>, TryCollect<Self::Stream<'conn>, Vec<U>>>
where U: Send, Conn: AsyncConnectionCore, Self: LoadQuery<'query, Conn, U> + 'query,

Executes the given query, returning a Vec with the returned rows. Read more
Source§

fn load_stream<'conn, 'query, U>( self, conn: &'conn mut Conn, ) -> Self::LoadFuture<'conn>
where Conn: AsyncConnectionCore, U: 'conn, Self: LoadQuery<'query, Conn, U> + 'query,

Executes the given query, returning a [Stream] with the returned rows. Read more
Source§

fn get_result<'query, 'conn, U>( self, conn: &'conn mut Conn, ) -> AndThen<Self::LoadFuture<'conn>, LoadNext<Pin<Box<Self::Stream<'conn>>>>>
where U: Send + 'conn, Conn: AsyncConnectionCore, Self: LoadQuery<'query, Conn, U> + 'query,

Runs the command, and returns the affected row. Read more
Source§

fn get_results<'query, 'conn, U>( self, conn: &'conn mut Conn, ) -> AndThen<Self::LoadFuture<'conn>, TryCollect<Self::Stream<'conn>, Vec<U>>>
where U: Send, Conn: AsyncConnectionCore, Self: LoadQuery<'query, Conn, U> + 'query,

Runs the command, returning an Vec with the affected rows. Read more
Source§

fn first<'query, 'conn, U>( self, conn: &'conn mut Conn, ) -> AndThen<<Self::Output as LoadQuery<'query, Conn, U>>::LoadFuture<'conn>, LoadNext<Pin<Box<<Self::Output as LoadQuery<'query, Conn, U>>::Stream<'conn>>>>>
where U: Send + 'conn, Conn: AsyncConnectionCore, Self: LimitDsl, Self::Output: LoadQuery<'query, Conn, U> + Send + 'query,

Attempts to load a single record. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Scoped for T
where T: Send + Sync + 'static,

Source§

fn scope(ctx: &PolicyContext) -> ScopeQuery<'_, Self>

Open a deferred ScopeQuery for this type. Resolves the registered scope at .load() time, not here.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WindowExpressionMethods for T

Source§

fn over(self) -> Self::Output
where Self: OverDsl,

Turn a function call into a window function call Read more
Source§

fn window_filter<P>(self, f: P) -> Self::Output
where P: AsExpression<Bool>, Self: FilterDsl<<P as AsExpression<Bool>>::Expression>,

Add a filter to the current window function Read more
Source§

fn partition_by<E>(self, expr: E) -> Self::Output
where Self: PartitionByDsl<E>,

Add a partition clause to the current window function Read more
Source§

fn window_order<E>(self, expr: E) -> Self::Output
where Self: OrderWindowDsl<E>,

Add a order clause to the current window function Read more
Source§

fn frame_by<E>(self, expr: E) -> Self::Output
where Self: FrameDsl<E>,

Add a frame clause to the current window function Read more
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more