下面显示的是英文原文。
Testing
TestApp::start runs the service's app on RedisBroker connected to a Redis modelled in process, groups, lists and channels included.
A test hands the harness the app main runs, unchanged, and addresses the broker by its production type, tb.broker::<RedisBroker>().
The testing feature in [dev-dependencies] adds the in-process mode, and turns on the core's harness with it.
The connected broker is the production one.
Every command it sends, a publish, a group read, an acknowledgement, a delay-queue sweep, reaches the model as that command and is answered as Redis answers it.
The model reads every setting off the broker.
Its address is parsed as the connect parses it, and the pool size, the default group and the cluster topology carry over.
It answers as Redis 8.4, so a claiming subscription mounts.
It keeps key types: a write or a read of the wrong type fails with WRONGTYPE.
It keeps consumer groups with their cursor, pending entries and delivery counts, the group's start_id, XAUTOCLAIM and XREADGROUP ... CLAIM at their idle thresholds, and a seek.
It keeps lists with the processing list and its recovery set, sorted sets, key expiry and MULTI / EXEC.
Pub/Sub channels, globs and sharded channels deliver to the subscriptions open at that moment and store nothing for one that is not.
On a cluster topology it refuses a command or a transaction that spans hash slots.
It runs no scripts: EVAL, EVALSHA, FCALL and a command it does not know fail with an error naming them.
No handshake runs, so AUTH, HELLO and TLS are for a live test.
A test's input goes where the default publisher writes its name: an XADD, or the LPUSH or PUBLISH of the subscription that reads the name.
A publish through the harness returns once every handler it woke has settled, a reply included, so the assertions that follow read finished state.
published::<T>(name) reads every write to the name back, a requeued copy and an entry a delay queue adds back included.
The model's time is the test's clock: entry ids, idle times and the scores of the delay queue and the recovery set stand still on a paused clock, and tb.advance(by) moves them.
A handler's own commands, through FredPool or a pipeline window, reach the model too.
Example
The dependency line:
[dev-dependencies]
ruststream-fred = { version = "0.7", features = ["testing"] }
The first test publishes a payment and reads how the handler settled it.
- Macros
- Manual
#[tokio::test]
async fn a_paid_payment_is_accepted() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
let payment = Payment { id: 1, amount: 100 };
tb.broker::<RedisBroker>()
.message(&payment)
.publish()
.await?;
tb.broker::<RedisBroker>()
.subscriber("payments")
.assert_called_once()
.with(&payment)
.settled(HandlerOutcome::ack());
Ok(())
}
#[tokio::test]
async fn a_paid_payment_is_accepted() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
let payment = Payment { id: 1, amount: 100 };
tb.broker::<RedisBroker>()
.message(&payment)
.publish()
.await?;
tb.broker::<RedisBroker>()
.subscriber("payments")
.assert_called_once()
.with(&payment)
.settled(HandlerOutcome::ack());
Ok(())
}
A settlement is answered with a receipt on the stream receipts, and the test reads the receipt back.
- Macros
- Manual
#[tokio::test]
async fn a_settlement_is_answered_with_a_receipt() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.broker::<RedisBroker>()
.message(&Settlement { id: 3 })
.publish()
.await?;
tb.broker::<RedisBroker>()
.published::<Receipt>("receipts")
.assert_called_once()
.with(&Receipt { id: 3 });
Ok(())
}
#[tokio::test]
async fn a_settlement_is_answered_with_a_receipt() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.broker::<RedisBroker>()
.message(&Settlement { id: 3 })
.publish()
.await?;
tb.broker::<RedisBroker>()
.published::<Receipt>("receipts")
.assert_called_once()
.with(&Receipt { id: 3 });
Ok(())
}
The model's time is the test's clock.
Entry ids, idle times and the scores of a delay queue stand still on a paused clock, and tb.advance(by) moves them.
A declined invoice comes back once the clock has moved past its delay.
- Macros
- Manual
#[tokio::test(start_paused = true)]
async fn a_declined_invoice_comes_back_after_the_delay() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.broker::<RedisBroker>()
.message(&Invoice { id: 4 })
.publish()
.await?;
tb.broker::<RedisBroker>()
.subscriber("invoices")
.assert_called_once()
.settled(HandlerOutcome::retry_after(RETRY_DELAY));
// The delay queue reads the test's clock, so the retry is due once the clock moves.
tb.advance(RETRY_DELAY).await?;
tb.broker::<RedisBroker>()
.subscriber("invoices")
.assert_called(2)
.settled(HandlerOutcome::ack());
Ok(())
}
#[tokio::test(start_paused = true)]
async fn a_declined_invoice_comes_back_after_the_delay() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.broker::<RedisBroker>()
.message(&Invoice { id: 4 })
.publish()
.await?;
tb.broker::<RedisBroker>()
.subscriber("invoices")
.assert_called_once()
.settled(HandlerOutcome::retry_after(RETRY_DELAY));
// The delay queue reads the test's clock, so the retry is due once the clock moves.
tb.advance(RETRY_DELAY).await?;
tb.broker::<RedisBroker>()
.subscriber("invoices")
.assert_called(2)
.settled(HandlerOutcome::ack());
Ok(())
}