今回解決したい問題
reqwestのドキュメント の最初のサンプルをテスト実行することを考えます。
let body = reqwest::get("https://www.rust-lang.org")
.await?
.text()
.await?;
println!("body = {body:?}");
まず Cargo.toml
に依存関係を追加して、
cargo new --lib test-async
cd test-async
cargo add reqwest
冒頭のコードを実行する関数を作ります。
fn my_func() {
let body = reqwest::get("https://www.rust-lang.org")
.await?
.text()
.await?;
println!("body = {body:?}");
}
cargo check
を実行すると次のエラーが出ます。
only allowed inside `async` functions and blocks
関数を async
で定義することでこのエラーを解消します。
async fn my_func() { // <- 頭に async を付与
let body = reqwest::get("https://www.rust-lang.org")
.await?
.text()
.await?;
println!("body = {body:?}");
}
するとまた別のエラーが出ます。
this function should return `Result` or `Option` to accept `?`
戻り値の型を Result
(もしくは Option
)にする必要があります。
ところで正常時の戻り値は自分で制御できますが、エラー時は ?
が制御しているので型がわかりません。とりあえず ()
でおいて修正します。
async fn my_func() -> Result<String, ()> {
// <- エラー時の方がわからないので () で仮置き
let body = reqwest::get("https://www.rust-lang.org")
.await?
.text()
.await?;
println!("body = {body:?}");
Ok("OK".to_string()) // <- 正常時は "OK" を返す
}
そうすると当然
`?` couldn't convert the error to `()`
というエラーが出ます。 そしてよく見るともう少し詳細な出力があります。
the trait `From<reqwest::Error>` is not implemented for `()`, which is required by `Result<(), ()>: FromResidual<Result<Infallible, reqwest::Error>>`
このメッセージから察するに、どうもエラーの型は reqwest::Error
っぽいです。これで書き換えてみます。
async fn my_func() -> Result<String, reqwest::Error> {
// <- エラーの型を reqwest::Error に書き換え
let body = reqwest::get("https://www.rust-lang.org")
.await?
.text()
.await?;
println!("body = {body:?}");
Ok("OK".to_string())
}
これでやっとエラーがなくなりました。
この関数に対してテストコードを書いていきましょう。 自動生成されていたテンプレートを書き換えてみます。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
// my_func() は Result<String, reqwest::Error> を返しているので unwrap() する
let result = my_func().unwrap();
assert_eq!(result, "OK");
}
}
cargo test
を実行すると次のエラーメッセージ。
help: consider `await`ing on the `Future` and calling the method on its `Output` let result = my_func().await.unwrap(); ++++++
言われるまま .await
を付けてみます。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = my_func().await.unwrap(); // <- await
assert_eq!(result, "OK");
}
}
すると次はこんなエラーが。
`await` is only allowed inside `async` functions and blocks
await
が使えるのは async
な関数の中でだけ、と。
じゃあ async
付ければ良いのか、と
#[cfg(test)]
mod tests {
use super::*;
#[test]
async fn it_works() { // <- async を付与
let result = my_func().await.unwrap();
assert_eq!(result, "OK");
}
}
すると、
async functions cannot be used for tests
テストの関数は async
にできない、と。じゃあどうすれば良いんだというのがわからない、というのが今回の問題です。
対応
答えは、「テスト実行用に非同期ランタイムを導入する」ということのようです。
今回は tokio を利用してみます。 ドキュメントを読むとテスト用の説明が書いてあります。 今回利用する tokio であればこのあたり:
というわけでやってみます。
cargo add --dev tokio --features='macros'
で dev-dependencies
に tokio を追加し、テストコードを次のように書き換えます。
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test] // <- test から tokio::test に変更
async fn it_works() {
let result = my_func().await.unwrap();
assert_eq!(result, "OK");
}
}
cargo test
を実行すると…テストが pass しました!
今回のコード: