Rustでは、数百行で高速かつ型安全なHTTPサーバーを実装できます。一方で、API契約の検証までRustツールチェインだけで回すと、cargo test の再実行、統合テストの保守、フロントエンド向けモックの二重実装がボトルネックになります。APIとして出荷するなら、実行中のRustサーバーに対してHTTPレベルで検証できるフィードバックループを用意しておくべきです。
このガイドでは、Apidog を使ってRust APIをテストする実装手順をまとめます。AxumまたはActix-webのサーバーをApidogに接続し、リクエスト作成、Serde JSONの検証、JWT認証、未完成エンドポイントのモック化、CIでの契約テスト実行までを一通り構築します。
Postmanやcurl中心の運用から移行する場合でも、Apidogでは保存済みリクエストからOpenAPI仕様を生成し、共有可能なモックURLやチーム環境を使えます。Postmanからの移行に関する内容 は別記事に譲り、ここではRust APIのテストに集中します。
TL;DR
- Rustサーバーをローカルで起動し、Apidog環境に
http://localhost:3000をbaseUrlとして登録する。 -
GET /healthzを最初のスモークテストとして保存する。 - Serde構造体に対応するJSONリクエストを作り、レスポンス形状をテストスクリプトで検証する。
- JWTはプリリクエストスクリプトで発行し、
{{token}}をフォルダレベルのBearer認証に設定する。 - 未完成のRustハンドラーはApidogのモックで先に契約を公開する。
- テストシナリオとして保存し、
apidog-cliでCIから実行する。
Rustツールチェイン外でRust APIをテストする理由
cargo test はRustコードの検証には有効です。ただし、HTTP APIの契約を確認するには次のような課題があります。
- コンパイルとテスト実行が重い
- Rust以外のメンバーがテスト内容を確認しづらい
- ステータスコード、ヘッダー、JSON形状、エラー形式をHTTP視点で管理しにくい
- フロントエンド向けモックを別途実装しがち
たとえばAxumでハンドラーを直接テストする場合、tower::ServiceExt::oneshot を使った統合テストを複数書くことになります。さらにフロントエンドが同じレスポンスを使いたい場合、別途JavaScriptやモックサーバーで再実装することもあります。
Apidogを使うと、実行中のRustバイナリに対してHTTP契約を1か所で管理できます。
主なメリットは次の3つです。
契約チェックをビルドから切り離せる
Apidogは実行中のサーバーにHTTPリクエストを送ります。rustcの完了を待たずに、APIレスポンスの形状を確認できます。モックをチームで共有できる
未完成のハンドラーでも、合意済みのJSONを返すURLをフロントエンドに渡せます。保存済みリクエストからOpenAPIを生成できる
すべてのルートにutoipaやaideのアノテーションを書く前でも、Apidog上のリクエストからOpenAPI 3.1ドキュメントを出力できます。
ステップ1:RustサーバーをApidog環境として追加する
まずRust APIを起動します。Axumの最小構成は次のとおりです。
use axum::{routing::get, Router};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let app = Router::new().route("/healthz", get(|| async { "ok" }));
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Apidogで新しいプロジェクトを作成し、環境管理から Rust Local 環境を追加します。
| 変数 | 値 |
|---|---|
baseUrl |
http://localhost:3000 |
token |
空のまま |
apiVersion |
v1 |
ステージング環境がある場合は、別途 Rust Staging を作り、デプロイ済みのベースURLを設定します。以後、リクエストURLには {{baseUrl}} を使えば、環境の切り替えだけでローカルとステージングを行き来できます。
ステップ2:最初のエンドポイントを叩く
Apidog内に Rust API フォルダを作成し、新しいリクエストを追加します。
- メソッド:
GET - URL:
{{baseUrl}}/healthz
送信して、次のレスポンスを確認します。
- ステータス:
200 - ボディ:
ok
このリクエストを health-check として保存します。以後のテストシナリオでは、最初にこのリクエストを実行して、サーバー起動と環境変数が正しいことを確認します。
接続拒否になる場合は、次を確認してください。
- Rustサーバーが起動しているか
- ポートが
3000で合っているか -
127.0.0.1ではなく0.0.0.0にバインドしているか
ローカルのApidogやDockerコンテナからアクセスする場合、TcpListener::bind("0.0.0.0:3000") にしておくと接続トラブルを避けやすくなります。
ステップ3:Serde JSONリクエストとレスポンスをテストする
次に、JSONを受け取りJSONを返す POST /users を追加します。
use axum::{extract::Json, routing::post, Router};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u64,
name: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
Json(User {
id: 1,
name: payload.name,
email: payload.email,
})
}
let app = Router::new().route("/users", post(create_user));
Apidogでリクエストを作成します。
- メソッド:
POST - URL:
{{baseUrl}}/users - Body: JSON
{
"name": "Ada Lovelace",
"email": "ada@example.com"
}
送信し、User JSONが返ることを確認したら、create-user として保存します。
次に「テスト」タブでレスポンスを検証します。
pm.test("Status is 200", () => {
pm.expect(pm.response.code).to.eql(200);
});
pm.test("Body has id, name, email", () => {
const body = pm.response.json();
pm.expect(body).to.have.property("id");
pm.expect(body.name).to.eql("Ada Lovelace");
pm.expect(body.email).to.match(/^[^@]+@[^@]+$/);
});
このテストにより、Rust側でSerde属性を変更してレスポンス形状が変わった場合、デプロイ前に契約の破壊を検出できます。
ステップ4:Serdeのリジェクションケースをカバーする
正常系だけでなく、不正な入力もApidogに保存しておきます。Axumでは、JSON抽出に失敗した場合、デフォルトで 422 Unprocessable Entity が返ります。
次の3つのリクエストを作成します。
| リクエスト | ボディ | 期待結果 |
|---|---|---|
create-user-missing-email |
{ "name": "Ada" } |
422、ボディに missing field email の記述 |
create-user-extra-field |
{ "name": "Ada", "email": "a@b.c", "admin": true } |
#[serde(deny_unknown_fields)] がない場合は 200、ある場合は 422
|
create-user-wrong-type |
{ "name": 1, "email": "a@b.c" } |
422、invalid type: integer の記述 |
各リクエストにステータスコードのアサーションを追加します。
例:
pm.test("Status is 422", () => {
pm.expect(pm.response.code).to.eql(422);
});
これにより、入力バリデーションの仕様をApidog上に明文化できます。後から deny_unknown_fields を有効にした場合も、公開契約の変更としてテスト失敗で検出できます。
ステップ5:JWTで保護されたルートをテストする
多くのRust APIでは、認証ミドルウェアの背後にハンドラーがあります。たとえば、Cookie内のJWTを検証するAxumハンドラーは次のようになります。
use axum::{Json, http::StatusCode};
use axum_extra::extract::cookie::PrivateCookieJar;
use jsonwebtoken::{decode, DecodingKey, Validation};
async fn me(jar: PrivateCookieJar) -> Result<Json<User>, StatusCode> {
let token = jar.get("token").ok_or(StatusCode::UNAUTHORIZED)?;
let claims = decode::<Claims>(
token.value(),
&DecodingKey::from_secret(b"secret"),
&Validation::default(),
)
.map_err(|_| StatusCode::UNAUTHORIZED)?;
Ok(Json(User {
id: claims.claims.sub,
name: "Ada".into(),
email: "ada@example.com".into(),
}))
}
Apidogでは、JWTを毎回手作業で作る必要はありません。フォルダにプリリクエストスクリプトを設定します。
const jwt = require("jsonwebtoken");
const token = jwt.sign(
{
sub: 1,
exp: Math.floor(Date.now() / 1000) + 3600
},
"secret"
);
pm.environment.set("token", token);
次にフォルダ設定で認証を設定します。
- 認証タイプ: Bearer Token
- 値:
{{token}}
これで、フォルダ内のすべてのリクエストが実行前に新しいJWTを生成し、Bearerトークンとして送信します。
JWT認証のテスト手順をより詳しく確認したい場合は、APIでのJWT認証をテストする方法 も参照してください。
ステップ6:ストリーミングとServer-Sent Eventsをテストする
RustのWebフレームワークはストリーミングレスポンスも扱えます。Axumの Sse は futures::Stream をラップし、text/event-stream としてチャンクを返します。
SSEのワイヤフォーマットは、一般的に次のような形式です。
data: { ... }
Apidogでは通常のGETリクエストとしてSSEエンドポイントを作成できます。レスポンスの Content-Type が text/event-stream の場合、レスポンスパネルでストリーミング内容を確認できます。
確認すべきポイントは次のとおりです。
- 最初のチャンクが期待時間内に到着するか
-
event: doneのような完了イベントが発行されるか - ストリームが無限に続かないか
- リクエスト設定でタイムアウトを設定し、完了しないストリームを失敗扱いにできるか
WebSocketを使っている場合も、ApidogのWebSocketリクエストタイプを使って接続、メッセージ送信、レスポンス検証を保存できます。
ステップ7:並行フロントエンド開発のためにRust APIをモック化する
フロントエンド開発は、Rustのコンパイル時間よりも「まだハンドラーが存在しない」ことにブロックされがちです。Apidogのモックを使えば、Rust側の実装前に合意済みレスポンスを返すURLを共有できます。
create-user リクエストを右クリックし、「スマートモック」を有効にします。Apidogは次のようなモックURLで User レスポンスを返します。
https://mock.apidog.com/m1/<projectId>/users
保存済みの例に基づいたレスポンスが返るため、フロントエンドは実際のRustサーバーと同じように POST できます。
動的なレスポンスが必要な場合は、「高度なモック」でスクリプトを書きます。
return {
id: Math.floor(Math.random() * 10000),
name: body.name,
email: body.email,
createdAt: new Date().toISOString()
};
このモックは、リクエストボディの name と email を使い、生成した id と createdAt を返します。
Rustハンドラーが完成したら、フロントエンドはベースURLを http://localhost:3000 に戻すだけです。考え方は他のランタイムでも同じで、Spring Boot APIの構築とテスト や 一般的なAPIテストワークフロー でも同様のパターンを使えます。
ステップ8:CIテストシナリオとして保存する
Apidogのテストシナリオでは、複数のリクエストを順番に実行し、変数を共有できます。Rust API用に次のシナリオを作成します。
-
health-checkを実行し、200をアサートする。 -
create-userを実行し、200をアサートし、body.idを変数に保存する。 -
create-user-missing-emailを実行し、422をアサートする。 -
meをJWTプリリクエスト付きで実行し、200をアサートする。 - SSEリクエストを実行し、5秒以内に完了することを確認する。
シナリオをJSONとしてエクスポートし、リポジトリに保存します。
tests/apidog/contract.json
CIではRustサーバーを起動してから、apidog-cli で契約テストを実行します。
- name: Run API contract tests
run: |
cargo build --release
./target/release/myserver &
sleep 2
apidog-cli run tests/apidog/contract.json --env "Rust Local"
これで、ハンドラーに変更を加えるPRは、ライブのRustバイナリに対してAPI契約テストを実行するようになります。Serdeのリネーム、ステータスコードの変更、JWT検証ロジックの変更などで公開契約が壊れた場合、マージ前に検出できます。
ステップ9:保存済みリクエストからOpenAPIを生成する
リクエストセットが安定したら、ApidogのエクスポートメニューからOpenAPI 3.1を選択します。保存済みリクエストと送信ボディの例をもとに、仕様ドキュメントを生成できます。
生成したOpenAPIは、次の用途に使えます。
- TypeScriptクライアント生成
- Swift / Kotlin / Pythonクライアント生成
- フロントエンドとの契約共有
- 外部パートナー向けAPI仕様書
Rustリポジトリに仕様を含めたい場合は、CIから apidog-cli export を実行し、openapi.json として保存します。
apidog-cli export --format openapi31 --output openapi.json
これにより、コードとは別に、API利用者が参照できる最新の契約ファイルを管理できます。
FAQ
ApidogはAxumとActix-webの両方で動作しますか?
はい。ApidogはHTTPを扱うため、Rustフレームワークには依存しません。Axum、Actix-web、Rocket、Warp、Poem、Locoなど、HTTPリクエストに応答するサーバーであれば同じように使えます。
ローカルテストでは、必要に応じて 127.0.0.1 ではなく 0.0.0.0 にバインドしてください。
パニックを起こすハンドラーはどうテストしますか?
tower-http の CatchPanicLayer をルーターの前に入れると、パニックを 500 レスポンスに変換できます。そのうえで、パニックパスをトリガーするApidogリクエストを作成し、500 をアサートします。
ラップしない場合、接続が切断され、Apidogはネットワークエラーとして表示します。これもAPI契約として扱えます。
Docker内のRustバイナリに対してApidogを実行できますか?
はい。baseUrl をコンテナの公開ポートに向ければ実行できます。
Docker Compose内で実行している場合は、Apidogランナーを同じネットワークに置くか、ホストにマッピングされたポートを使います。
gRPCはテストできますか?
はい。ApidogにはgRPCリクエストタイプがあります。.proto ファイルをインポートし、サービスとメソッドを選択して、リクエストペイロードを送信できます。
認証、環境変数、テストシナリオの考え方はREST APIと同じです。
Apidogのテストシナリオは cargo test を置き換えますか?
いいえ。Rustコードの単体テストはRust内に残すべきです。
Apidogは、実行中のHTTP APIの契約を検証します。両者は検出するバグが異なります。
-
cargo test: 関数、ドメインロジック、型レベルの検証 - Apidog: レスポンス形状、ステータスコード、ヘッダー、CORS、認証、エラー形式の検証
両方を使うのが安全です。
ApidogはRustのオープンソースプロジェクトで無料ですか?
はい。Apidogクライアントは個人および小規模チーム向けに無料で利用できます。テストシナリオ、モック、OpenAPIエクスポートも無料ティアに含まれます。
まとめ
Rust APIには、コンパイラーを待たずにHTTP契約を検証できるフィードバックループが必要です。Apidogを使うと、実行中のAxumまたはActix-webサーバーに対して、リクエスト、アサーション、モック、CIシナリオを1つのワークフローで管理できます。
まずは GET /healthz、POST /users、認証付きルート、エラーケースをApidogに保存してください。その時点で、APIの変更は「実行時の予期せぬ破壊」ではなく、「CIで検出できる契約差分」になります。
Apidogをダウンロード し、Rustサーバーの baseUrl を設定してください。10分程度で、cargo から切り離されたAPI契約テストと、フロントエンドに共有できるモック環境を用意できます。
Top comments (0)