跳到主要内容
英文版本为准

本页译自英文,由模型翻译。若与英文原文存在差异,以英文原文为准。

用 TestApp 测试服务

TestApp 在进程内运行商店自己的应用:测试发布一个订单,然后检查每个订阅者做了什么、每个主题收到了什么。

测试用 TestApp::start 启动 main 运行的那个应用,不做任何改动。 Broker 在进程内连接,每个订阅都运行在测试的运行时上。 测试中的一次发布会等到它引发的一切都结算之后才返回,所以下一行就能检查结果。 断言读取两份记录:每个订阅者收到了什么、如何结算,以及每个主题上发布了什么。 在暂停的时钟上,advance 让时间前进,所以一秒或一小时的延迟都不花费任何时间。 测试需要 crate 的 testing feature,通常作为开发依赖。

一个订单从头到尾​

测试带着请求消息头下一个订单,并一路跟踪到顾客。 支付请求以 CBOR 传递,所以断言用 with_codec 解码它。 accept 和 dispatch 各发送一条通知,send 对两条都 ack。

main.rs
#[tokio::test]
async fn a_placed_order_is_charged_shipped_and_announced() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.message(&order(7, mugs(12)))
.with_headers(&meta("req-7"))
.publish()
.await?;

let broker = tb.broker::<MemoryBroker>();
broker
.published::<PaymentRequest>("payments.requested")
.assert_called_once()
.with_codec(
&CborCodec,
&PaymentRequest {
order: 7,
customer: "ada@example.com".to_owned(),
lines: mugs(12),
amount_cents: 14_400,
request_id: "req-7".to_owned(),
},
);
broker
.published::<Notice>("notices")
.assert_called(2)
.with(&Notice {
customer: "ada@example.com".to_owned(),
text: "Order 7 is on its way: TRK-7-2.".to_owned(),
});
broker
.subscriber("notices")
.assert_called(2)
.settled(HandlerOutcome::ack());
Ok(())
}

止步的订单​

没有订单行的订单停在 accept:不收款,也不通知顾客。

main.rs
#[tokio::test]
async fn an_order_with_no_lines_goes_no_further() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.message(&order(8, Vec::new()))
.with_headers(&meta("req-8"))
.publish()
.await?;

let broker = tb.broker::<MemoryBroker>();
broker
.published::<PaymentRequest>("payments.requested")
.assert_not_called();
broker.published::<Notice>("notices").assert_not_called();
Ok(())
}

不必等待的延迟​

每秒只接受一笔收款的服务商会以 Busy 拒绝第二个请求。 with_state 用这个服务商启动应用,代替 on_startup 打开的那个。 service.rs 中的 test_broker 返回测试构建商店所用的 Broker,所以测试只在一处写明 Broker。 时钟从暂停状态开始,advance(BACKOFF) 让被拒绝的请求回来。

payments.rs
#[tokio::test(start_paused = true)]
async fn a_busy_provider_is_asked_again_after_the_backoff() -> Result<(), Box<dyn Error>> {
// A provider that takes one charge a second.
let state = Shop {
gateway: Arc::new(Gateway::new(100_000, 1)),
..Shop::open().await?
};
let tb = TestApp::with_state(shop(test_broker()), |_| state).await?;
let broker = tb.broker::<MemoryBroker>();
for order in [1, 2] {
broker
.message(&request(order, 500))
.with_codec(CborCodec)
.publish()
.await?;
}
broker
.subscriber("payments.requested")
.assert_called(2)
.settled(HandlerOutcome::retry_after(BACKOFF));

tb.advance(BACKOFF).await?;
broker
.subscriber("payments.requested")
.assert_called(3)
.settled(HandlerOutcome::ack());
broker
.published::<Paid>("payments.settled")
.assert_called(2);
Ok(())
}

死信主题​

从不应答的服务商让每次投递都失败。 第五次投递按到达时的原样进入 payments.dead。

payments.rs
#[tokio::test]
async fn a_silent_provider_sends_the_request_to_the_dead_letter_topic()
-> Result<(), Box<dyn Error>> {
let state = Shop::open().await?;
state.gateway.go_offline();
let tb = TestApp::with_state(shop(test_broker()), |_| state).await?;
let broker = tb.broker::<MemoryBroker>();
broker
.message(&request(3, 500))
.with_codec(CborCodec)
.publish()
.await?;

broker.subscriber("payments.requested").assert_called(5);
broker
.published::<PaymentRequest>("payments.dead")
.assert_called_once()
.with_codec(&CborCodec, &request(3, 500));
Ok(())
}

运行两种形式的测试:

cargo test --example guide --features testing,macros,memory,json,cbor,asyncapi
cargo test --example manual_guide --no-default-features --features testing,memory,json,cbor,asyncapi

两个目录中的测试相同,它们在商店的两种形式上都能通过。