---
title: "JestとOpenAPIでAPIテストを自動化する方法｜jest-openapiの3.1非対応とSchemathesis・StepCIの使い分け"
url: "https://www.issoh.co.jp/tech/details/2273/"
published: 2024-05-24
updated: 2026-10-12
categories: ["テスト"]
publisher: "株式会社一創"
---

# JestとOpenAPIでAPIテストを自動化する方法｜jest-openapiの3.1非対応とSchemathesis・StepCIの使い分け

OpenAPI（旧Swagger）の仕様書をテストの正解として使えば、APIの実装が仕様からずれた時点でCIで止められます。Jestでこれを行う定番は `jest-openapi` ですが、最終リリースは2022年1月の0.14.2で、**OpenAPI 3.1の仕様書も3.0の規則で検査する**ため、`type: [string, "null"]` を含む仕様は読み込みの段階でエラーになります。本記事では、Jest 30で実行した結果をもとに、3.0で使う手順、3.1の応答を照合する代替、入力を自動生成するSchemathesis、シナリオを書くStepCIの使い分けを整理します。

## まとめ：OpenAPI仕様でAPIテストを自動化するときの選び方

- **仕様が3.0以下なら jest-openapi**。`expect(res).toSatisfyApiSpec()` の1行で、ステータスとボディを仕様に照合できます。
- **仕様が3.1なら、読み込めたかどうかだけで対応可否を判断しない**。jest-openapiは3.1に正式対応しておらず、`type` の配列指定を含む仕様は `Invalid OpenAPI spec` で止まります。3.1の仕様を使うなら、Ajvの2020-12対応版で応答ボディだけを照合する自作マッチャが手早い代替です。
- 入力値を変えて叩き、仕様違反の受け入れや5xxを探すのは **Schemathesis**。手書きのテストでは思いつかない値を仕様から生成します。
- ログインを挟む複数リクエストの流れは **StepCI** のYAMLで書けますが、2024-06-10の2.8.2を最後にリリースが無く、既定で利用状況を外部へ送信します。
- どの方法も「仕様書が更新され続けること」が前提です。仕様を直さずに実装だけ変える現場では、テストが無効化されて終わります。

## 仕様駆動のAPIテストで照合できる3つの対象

OpenAPIを使うテストは、何を照合するかで道具が分かれます。最初にどれが欲しいかを決めると、ツールを重ねて入れる無駄がなくなります。

| 照合する対象     | 見つかる不具合         | 向く道具             |
| ---------- | --------------- | ---------------- |
| 応答の形       | 型違い・必須項目の欠落     | jest-openapi／Ajv |
| 不正な入力の拒否   | 不正値を200で受ける・5xx | Schemathesis     |
| 複数リクエストの流れ | トークン引き回しの失敗     | StepCI           |

仕様書そのものの書き方は[OpenAPIの基本とSwaggerとの違い、3.0・3.1・3.2の差分](/tech/details/10408/)を参照してください。フロントエンドとの並行開発で仕様からモックを立てる用途（Prism等）はテストではなく[APIモッキングとモックサーバーの方式選定](/tech/details/16308/)の領域です。

## jest-openapiでレスポンスを仕様に照合する手順（OpenAPI 3.0まで）

[jest-openapi](https://www.npmjs.com/package/jest-openapi) はJestに `toSatisfyApiSpec()` と `toSatisfySchemaInApiSpec()` の2つのマッチャを追加するライブラリです。READMEは対応クライアントとして axios・request-promise・supertest・superagent・chai-http を挙げています。

### 検証用のExpressアプリと仕様書

以降の例はすべて次の `app.js` を対象にします。`/users/2` だけ、わざと `id` を文字列で返します。

```
// app.js
const express = require('express');
const app = express();
app.use(express.json());

app.post('/login', (req, res) => {
  if (req.body.user === 'alice' && req.body.password === 'secret') return res.json({ token: 'abc123' });
  res.status(401).json({ message: 'invalid credentials' });
});
app.get('/me', (req, res) => {
  if (req.get('authorization') !== 'Bearer abc123') return res.status(401).json({ message: 'unauthorized' });
  res.json({ id: 1, name: 'Alice' });
});
app.get('/users/:id', (req, res) => {
  if (req.params.id === '2') return res.json({ id: '2', name: 'Bob' }); // 仕様違反: id が文字列
  if (req.params.id === '3') return res.json({ id: 3, name: 'Carol', nickname: null });
  res.json({ id: 1, name: 'Alice' });
});
module.exports = app;
```

仕様書 `openapi30.yaml` は `/users/{id}` だけを定義します。

```
openapi: 3.0.3
info: { title: Users, version: 1.0.0 }
paths:
  /users/{id}:
    get:
      parameters:
        - { name: id, in: path, required: true, schema: { type: integer } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/User' }
components:
  schemas:
    User:
      type: object
      required: [id, name]
      properties:
        id: { type: integer }
        name: { type: string }
        nickname: { type: string, nullable: true }
```

3.1版の `openapi31.yaml` は、1行目を `openapi: 3.1.0` に、`nickname` を `{ type: [string, "null"] }` に変えただけのものです。依存は `npm install --save-dev jest jest-openapi supertest` と `npm install express` で入ります。

### supertestでの最小構成

次のコードを `app.js` と同じディレクトリに `api.test.js` として保存します（CommonJS前提）。冒頭で仕様を1回読み込み、レスポンスをマッチャに渡し、`npx jest` で実行します。

```
const path = require('path');
const request = require('supertest');
const jestOpenAPI = require('jest-openapi').default;
const app = require('./app');

// 仕様はファイルの絶対パスかオブジェクトで渡す
jestOpenAPI(path.join(__dirname, 'openapi30.yaml'));

test('GET /users/1 は仕様どおり', async () => {
  const res = await request(app).get('/users/1');
  expect(res.status).toBe(200);
  expect(res).toSatisfyApiSpec();
});

test('User スキーマ単体の照合', () => {
  expect({ id: 1, name: 'Alice' }).toSatisfySchemaInApiSpec('User');
});
```

CommonJSで `require` するときは `.default` が必要です。付けないと `jestOpenAPI is not a function` になります。Node.jsのネイティブESMでも同じで、`import pkg from 'jest-openapi'` の `pkg` はオブジェクトなので、`pkg.default(...)` と呼びます。ts-jestやBabelでCommonJSへ変換する構成なら、READMEどおり `import jestOpenAPI from 'jest-openapi'` で関数を受け取れます。

### 仕様違反のときに出る失敗メッセージ

`/users/2` を照合すると、Jestは次のように落ちます。

```
expected received to satisfy the '200' response defined for endpoint 'GET /users/{id}' in your API spec
received did not satisfy it because: id must be integer
received contained: { body: { id: '2', name: 'Bob' } }
```

どのエンドポイントの、どのステータスの定義と、どの項目が合わないかが1回の実行でわかります。仕様に無いパスへのリクエストを照合した場合も `your API spec has no matching path` で失敗するため、ドキュメントに載っていないエンドポイントがテストに現れれば気づけます。Jest側のモックで依存を差し替える方法は[Jest mockImplementationの使い方](/tech/details/4065/)で扱っています。

## jest-openapiの保守停止とOpenAPI 3.1非対応

2026年時点で導入前に知っておくべき制約が2つあります。以下は検証環境（Node.js v26.5.0・Jest 30.5.2・jest-openapi 0.14.2）での結果です。

### 最終リリース0.14.2（2022年1月）と依存のaxios 0.21系

npmレジストリの公開日時は `0.14.2` が2022-01-03で、それ以降の版はありません。開発元の [openapi-library/OpenAPIValidators のコミット履歴](https://github.com/openapi-library/OpenAPIValidators/commits/master)は2022-03-29のドキュメント修正が最後です（2026-10-12確認）。リポジトリはアーカイブされていませんが、実質的に更新は止まっています。

依存ツリーにも影響があります。`jest-openapi` が依存する `openapi-validator 0.14.2` は axios を `^0.21.1` で指定しているため、インストールされるのは0.21系（検証時は0.21.4）に限られます。このため `npm audit` はaxiosについてSSRFや認証情報漏えいなどhighの勧告を複数出します。開発時だけ使うパッケージとはいえ、脆弱性ゼロをCIの条件にしている組織では導入時点で引っかかります。

### type配列を含む3.1仕様の読み込みエラー

READMEは「OpenAPI 2 and 3」に対応と書いていますが、リンク先は3.0.0の仕様です。内部で使う `openapi-schema-validator 9.3.1` は `openapi` の値を整数に変換して版を判定するため、`3.1.0` も「3」とみなし、同梱の3.0用メタスキーマで仕様を検査します。このメタスキーマは `type` に「文字列であること」しか求めていません。3.1では `nullable: true` が廃止され、JSON Schema 2020-12に合わせて `type: [string, "null"]` と配列で書くため、この形を含む仕様を渡すと、テストが1件も走らないうちに止まります。

```
Invalid OpenAPI spec: [
  {
    instancePath: '/components/schemas/User/properties/nickname/type',
    keyword: 'type',
    message: 'must be string'
  },
  ...
]
  at validateSpec (node_modules/openapi-validator/lib/openApiSpecFactory.ts:71:11)
```

一方、`type` が文字列であれば、3.0に無い値でも通ります。FastAPI 0.143.0は `openapi` の値として `3.1.0` を出力しますが、`Optional[str]` を `anyOf: [{type: string}, {type: "null"}]` で表します。`type: "null"` は本来3.0の仕様に無い値ですが、メタスキーマは文字列でさえあれば通すため、検証環境では読み込みに成功し、`nickname` に `null` を返す応答は通り、数値を返す応答は失敗しました（FastAPI側の仕様の見え方は[FastAPIのSwagger UI（/docs）の使い方](/tech/details/2246/)を参照）。つまり、落ちるかどうかは3.1の書き方を使っているかではなく、同梱メタスキーマの検査に引っかかるかで決まります。読み込めた場合も正式対応ではないため、null許容などの照合結果は、違反する応答を1件用意して本当に落ちるかを確かめてから使ってください。

3.1への公式対応はGitHubの[issue #305（2025-12-07起票）](https://github.com/openapi-library/OpenAPIValidators/issues/305)で要望されていますが、未対応のまま開いています。OpenAPI自体は[仕様書の改訂履歴](https://spec.openapis.org/oas/v3.2.1.html)のとおり3.1.0が2021-02-15、3.2.1が2026-09-10と先へ進んでいます。Vitest向けの `vitest-openapi`（1.0.3）も依存に `jest-openapi ^0.14.2` を持つため、同じ制約を引き継ぎます。

## OpenAPI 3.1の応答ボディをJestで照合するAjv 2020の自作マッチャ

3.1のスキーマオブジェクトはJSON Schema 2020-12を拡張したもの（上位集合）です。型・必須項目・`$ref` の参照といった2020-12の範囲なら、2020-12に対応した [Ajv](https://ajv.js.org/json-schema.html) に仕様書を丸ごと登録して応答ボディを検証できます。追加するのは `ajv`・`ajv-formats`・`js-yaml` の3つで、本体の `ajv` は8.20.0（2026年4月）、`js-yaml` は5.4.3（2026-10-06）と更新が続いています。

```
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const request = require('supertest');
const Ajv2020 = require('ajv/dist/2020').default;
const addFormats = require('ajv-formats').default;
const app = require('./app');

const spec = yaml.load(fs.readFileSync(path.join(__dirname, 'openapi31.yaml'), 'utf8'));
// discriminator や example など OpenAPI 独自のキーワードを無視させるため strict: false
const ajv = new Ajv2020({ strict: false, allErrors: true });
addFormats(ajv);
ajv.addSchema(spec, 'spec');

expect.extend({
  toMatchResponseSchema(res, specPath, method) {
    // JSON Pointer では ~ を ~0、/ を ~1 に置き換える
    const ptr = specPath.replace(/~/g, '~0').replace(/\//g, '~1');
    const ref = `spec#/paths/${ptr}/${method}/responses/${res.status}/content/application~1json/schema`;
    const validate = ajv.getSchema(ref);
    if (!validate) {
      return { pass: false, message: () => `仕様に ${method.toUpperCase()} ${specPath} の ${res.status} 応答がありません` };
    }
    const pass = validate(res.body);
    return { pass, message: () => ajv.errorsText(validate.errors) };
  },
});

test('GET /users/{id} は 3.1 の仕様どおり', async () => {
  const res = await request(app).get('/users/3');
  expect(res).toMatchResponseSchema('/users/{id}', 'get');
});
```

同じ3.1の仕様で、`nickname: null` を返す `/users/3` は通り、`id` を文字列で返す `/users/2` は `data/id must be integer` で失敗しました。スキーマ内の `$ref: '#/components/schemas/User'` も `spec` という名前で登録した文書の中で解決されます。

このマッチャは約20行で済む代わりに、汎用の応答バリデーターではありません。次の処理は持たないので、使う仕様がこれらに当たらないかを先に確かめてください。

- リクエストのパスから `/users/{id}` のテンプレートを探す処理（テスト側で仕様のパスを明示する）
- `responses` 直下の `$ref`、`2XX` のような範囲指定、`default` 応答へのフォールバック
- 実際の `Content-Type` の確認と、`application/json` 以外のメディアタイプ
- `readOnly`・`writeOnly` の方向の扱い（Ajvは注釈として無視するため、`required` に `writeOnly` の `password` を含む共有スキーマでは、`password` を返さない正しい応答が必須項目の欠落で落ちる。応答用のスキーマを分ける）
- 別ファイルを指す `$ref` の解決（必要なら先に `@apidevtools/swagger-parser` で1ファイルに束ねる）

パスの自動照合まで欲しい場合は既製の検証ライブラリが候補になりますが、Jestでの読み込みは事前に試してください。たとえば `openapi-backend 5.21.3` は、依存する `@apidevtools/json-schema-ref-parser` がESM形式のため、Jest 30の既定設定（CommonJS）では `Must use import to load ES Module` で読み込めませんでした。

## Schemathesisによる入力生成と仕様違反の検出

[Schemathesis](https://github.com/schemathesis/schemathesis) はPython製のCLIで、仕様のパラメータ定義から正常値と異常値を大量に生成し、応答が仕様に合うかを確かめます。公式ドキュメントは対応形式をOpenAPI 2.0（Swagger）・3.0・3.1・3.2とGraphQLとしており、ライセンスはMITです。2026年10月時点の最新は4.30.1（GitHubのリリース日時で2026-10-09 UTC）で、10月の最初の9日間だけで7回リリースされています。

### 実行コマンドと検出された不備

先に `node -e "require('./app').listen(48231, '127.0.0.1')"` でアプリを起動しておき、3.1の仕様を渡します。uvを導入しておけば、Pythonの仮想環境を手動で作らずに `uvx` で実行でき、`@` で版を固定すると結果を比べやすくなります。

```
uvx schemathesis@4.30.1 run openapi31.yaml --url http://127.0.0.1:48231 --max-examples 30
```

検証時は38件のテストケースが生成され（件数は乱数と設定で変わります）、次の不備が1件見つかりました。

```
- API accepted schema-violating request

    Invalid data should have been rejected
    Expected: 400, 401, 403, 404, 405, 406, 409, 415, 422, 428, 429, 5xx
    Invalid component: parameter `id` in path - incorrect type

Reproduce with:

    curl -X GET http://127.0.0.1:48231/users/null%2Cnull
```

仕様は `id` を `integer` と定義しているのに、実装は `/users/null,null` にも200を返していました。jest-openapiでは見つからない種類の不具合です。手書きでも異常入力の拒否は検証できますが、入力例を自分で列挙する必要があります。一方、Schemathesisは仕様から入力を生成して探索するため、想定していなかった値で「仕様が拒否すべき入力を拒否しているか」を確かめられます。検出項目は `--checks` で選べ、`negative_data_rejection`・`response_schema_conformance`・`not_a_server_error` などがあります。性質を定義して入力を生成する考え方は[プロパティベーステストの書き方と縮小・再現の仕組み](/tech/details/16259/)で詳しく扱っています。

### GitHub Actionsへの組み込み

公式のGitHub Action `schemathesis/action@v3`（v3.0.0は2026-03-11公開）があり、仕様のURLを渡すだけで動きます。CLIでは `--report junit` を付けると `schemathesis-report` ディレクトリにJUnit形式のXMLが出力され、CIのテスト結果画面に載せられます。最初から全項目を必須にすると既存APIの不備がまとめて出てCIが止まるため、`--exclude-checks` で段階的に有効化するのが現実的です。

## StepCIによる複数ステップのシナリオ検証

[StepCI](https://github.com/stepci/stepci) はテストをYAMLで宣言するCLIで、ライセンスはMPL-2.0です。前のステップの応答から値を取り出して後続に渡せるため、「ログインしてトークンを得る→そのトークンで自分の情報を取る」という流れを1ファイルで書けます。

### トークンを引き回すワークフローYAMLと終了コード

```
version: "1.1"
name: Login flow
env:
  host: http://127.0.0.1:48231
tests:
  login:
    steps:
      - name: login
        http:
          url: ${{env.host}}/login
          method: POST
          json:
            user: alice
            password: secret
          captures:
            token:
              jsonpath: $.token
          check:
            status: 200
      - name: me
        http:
          url: ${{env.host}}/me
          method: GET
          auth:
            bearer:
              token: ${{captures.token}}
          check:
            status: 200
            jsonpath:
              $.name: Alice
```

`npx stepci run login.yml` で実行すると、`captures` で取り出したトークンが2つ目のステップの `Authorization: Bearer` に入り、2ステップとも成功して終了コード0で終わりました。`check` に `schema` を書けば応答をJSON Schemaで照合でき、条件を満たさないステップがあると期待値と実際の応答を並べて表示し、終了コード5で終わります。CIのステップに置けばそのまま失敗として扱われます。

`stepci generate openapi31.yaml` で仕様から雛形も作れ、3.1の `type: [string, "null"]` もそのまま保持されました。ただし今回の仕様には `servers` を書いていなかったため、生成されたURLはホスト名を含まない相対パスで、パスパラメータには乱数が入りました。生成後はURLと値を確認してから実行してください。

### StepCIの最終リリース2.8.2と利用状況の送信

npmの `stepci` は2.8.2（2024-06-10）が最新で、[GitHubのコミット履歴](https://github.com/stepci/stepci/commits/main)も2024-08-03のドキュメント修正が最後です（2026-10-12確認）。配布物の `dist/lib/analytics.js` を読むと、コマンド実行のたびにPostHogへOS名・Node.jsのバージョン・実行したサブコマンド・実行環境（CI名かDockerかローカル）を送信しています。環境変数 `STEPCI_DISABLE_ANALYTICS` に何か値を入れると止まります。社外への通信に制限がある現場では、CI設定でこの変数を必ず定義してください。

保守が止まっている点を踏まえると、新規に採用するなら、シナリオを既存のJestテストの中でsupertestを続けて呼ぶ形で書くほうが依存を増やしません。GUIで組みたい場合は[PostmanとNewmanでのAPIテスト自動化](/tech/details/17392/)が選択肢になります。

## ツールの選び分けと導入を見送る場面

| 道具           | 対応する仕様    | 更新状況（2026-10時点・UTC） | 使う場面          |
| ------------ | --------- | ------------------- | ------------- |
| jest-openapi | 2.0・3.0   | 2022-01-03が最後       | 3.0の仕様と既存Jest |
| Ajv自作マッチャ    | 3.1の応答ボディ | Ajvは更新中             | 3.1の仕様と既存Jest |
| Schemathesis | 2.0〜3.2   | 2026-10-09          | 入力の拒否・5xxの検出  |
| StepCI       | 3.1から生成可  | 2024-06-10が最後       | YAMLでのシナリオ    |
| Dredd        | –         | アーカイブ済み             | 新規採用しない       |

以前よく推奨された [Dredd](https://github.com/apiaryio/dredd) はリポジトリがアーカイブされ、読み取り専用になっています。新しい解説記事でも推奨先に挙がっていることがありますが、新規採用の候補から外してください。

次のいずれかに当てはまる現場では、仕様駆動テストの導入を見送るか、先に別の手当てをします。

- **仕様書を更新する担当者がいない**。古い仕様を正解にしたテストは正しい実装を落とすため、誤検知が続いて `toSatisfyApiSpec` が無効化される原因になります。先に[スキーマファーストの運用（仕様の所有者と破壊的変更の検知）](/tech/details/16302/)を決めます。
- **サービス同士の互換性を守りたい**。提供側の仕様への適合は、利用側が実際に使う項目の保証にはなりません。利用側起点で確かめるなら[Pactによる消費者駆動契約テスト](/tech/details/16265/)が向きます。

なお、仕様をコードから自動生成している場合でも、実行時の応答が仕様どおりになる保証にはならないため、応答の照合は省かずに続けます。そのうえでSchemathesisによる入力側の検証を足すと、応答照合とは別の不具合も探せます。CIへの組み込みや品質ゲートの設計をまとめて見直したい場合は、[DevOps・CI/CD導入支援](/service/system/devops/)でテスト自動化を含めて相談を受け付けています。

## よくある質問

### 「openai api vs jest」で調べても比較記事が見つからないのはなぜですか？

OpenAI API（OpenAI社のLLMを呼び出すサービス）とJest（JavaScriptのテストフレームワーク）は用途が別で、比較の対象にならないためです。APIの仕様記述形式である「OpenAPI」を探していた場合は、表記を確かめてください。JestとOpenAPIは競合ではなく、OpenAPIの仕様をJestのテストで照合する組み合わせで使います。

### jest-openapiはOpenAPI 3.1の仕様で使えますか？

公式には対応していません。0.14.2は3.1の仕様も3.0の規則で検査するため、`type: [string, "null"]` のような3.1の書き方を含むと `Invalid OpenAPI spec` で止まります。FastAPIが出力する `anyOf` 形式のように読み込めるものもありますが、正式対応ではありません。3.1の仕様は、Ajvの2020-12対応版で応答ボディを照合するか、Schemathesisを併用します。

### Swagger 2.0の仕様書でもテストを自動化できますか？

できます。jest-openapiはSwagger 2.0（OpenAPI 2）の仕様を読み込めます。Schemathesisも2.0に対応しています。新しく書く仕様を3.1以降にしておくと、Ajvなど汎用のJSON Schemaツールで扱える範囲が広がります。

### StepCIは無料で使えますか？

CLIはMPL-2.0のオープンソースで、`npx stepci run` で無料で実行できます。ただし2024-06-10の2.8.2を最後にリリースが無く、既定で利用状況を送信するため、CIでは `STEPCI_DISABLE_ANALYTICS` を設定してください。

### JestとPostmanのどちらでAPIテストを書くべきですか？

アプリのリポジトリでコードと一緒にテストを管理し、プルリクエストごとに回すならJestです。コードを書かない担当者がGUIでリクエストを組み、テストとして共有したいならPostmanが向きます。Postmanのテストは `newman` でCIからも実行できます。

## 関連記事

- [OpenAPIとは？Swaggerとの違いと仕様書(YAML/JSON)の書き方｜3.0・3.1・3.2の差分](/tech/details/10408/)
- [スキーマファーストとは？OpenAPI起点の並行開発と破壊的変更検知を解説](/tech/details/16302/)
- [契約テストとは？Pactによる消費者駆動契約の実装とCI組み込みを実装者向けに解説](/tech/details/16265/)
- [プロパティベーステストとは？性質の書き方と縮小・再現の仕組みを実装者向けに解説](/tech/details/16259/)
- [Postmanとは？APIテスト自動化とNewmanでのCI組み込みまで実装手順](/tech/details/17392/)

---

出典: [JestとOpenAPIでAPIテストを自動化する方法｜jest-openapiの3.1非対応とSchemathesis・StepCIの使い分け](<https://www.issoh.co.jp/tech/details/2273/>)（株式会社一創）
