Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.
Тестирование сервиса с TestApp
TestApp запускает приложение магазина в процессе: тест публикует заказ и проверяет, что сделал каждый подписчик и что получила каждая тема.
Тест запускает то же приложение, что и main, без изменений, через TestApp::start.
Брокер подключается внутри процесса, и каждая подписка работает на рантайме теста.
Публикация из теста возвращает управление, когда все запущенные ею доставки завершены, поэтому следующая строка уже может проверять результат.
Проверки читают две записи: что получил каждый подписчик и каким исходом завершил доставку, и что было опубликовано в каждую тему.
Когда время в тесте приостановлено, advance сдвигает его вперёд, поэтому задержка в секунду или в час не занимает времени.
Для тестирования нужна фича testing крейта, обычно в dev-зависимостях.
Заказ от начала до конца
Тест оформляет заказ с заголовками запроса и прослеживает его до покупателя.
Запрос оплаты передаётся в CBOR, поэтому проверка декодирует его через with_codec.
accept и dispatch отправляют по уведомлению, и send подтверждает оба.
- Макросы
- Вручную
#[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(())
}
#[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: оплата не списывается, и покупатель не получает уведомлений.
- Макросы
- Вручную
#[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(())
}
#[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.
Функция test_broker из service.rs возвращает брокер, на котором тесты собирают магазин, поэтому брокер в тестах назван в одном месте.
Время в тесте с самого начала приостановлено, и advance(BACKOFF) возвращает отклонённый запрос.
- Макросы
- Вручную
#[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(())
}
#[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 в том виде, в каком пришла.
- Макросы
- Вручную
#[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(())
}
#[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
Тесты в обоих каталогах одинаковые, и они проходят на обеих формах магазина.