•20 min read

マイクロサービスモック戦略: MSW対WireMockガイド

マイクロサービスモック戦略: MSW対WireMockガイド

サードパーティのステージングAPIがランダムに503エラーを返し、CIビルドが失敗した経験があるなら、モックされていない統合テストの苦痛をご存知でしょう。

適切なHTTPモック戦略を選択することは、信頼できるテストスイートと、誰もが無視する不安定なテストスイートとの違いを分けるものです。Mock Service Worker (MSW) と WireMock を比較する場合、単に2つのライブラリを比較するだけでなく、完全に異なる2つの哲学を比較することになります。すなわち、フロントエンド/JSエコシステム向けのノードレベルのネットワークインターセプトと、ポリグロットなマイクロサービス環境向けのスタンドアロンプロキシサーバーです。

Audio Briefing
0:00 / 0:00

不安定性の問題:モック戦略が速度を決定する理由

ライブのサードパーティステージングAPIにアクセスする統合テストスイートは、レート制限、低速なネットワーク実行、ランダムなタイムアウトという時限爆弾を抱えています。エンジニアがWiFiなしで飛行機内でも実行できる、高速で決定論的なテストスイートが必要な場合、外部依存関係をモックする必要があります。

Microservices Mocking Architecture Tradeoffs

MSWは、msw/nodeモジュールを使用してNode.jsプロセスレベルで、またはService Worker APIを使用してブラウザ環境で、送信ネットワークリクエストをインターセプトすることで動作します。MSWはネットワーク呼び出しがNode.jsのネットワークソケットを離れる前にインターセプトするため、モック応答は物理的なTCP/IPネットワークインターフェースに入ることなく即座に返されます。

WireMockは、JVMプロセスまたはDockerコンテナとして実行されるスタンドアロンのHTTPモックサーバーとして動作します。クライアントアプリケーションは、TCPソケットを介してWireMockのホストポートに実際のHTTP呼び出しを行います。WireMockは、受信リクエストペイロードを登録されたJSONスタブマッピングと照合し、事前に設定されたHTTP応答を返します。

// Example: Setting up MSW 2.0 network handlers in a Node.js microservice test
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { fetchPaymentStatus } from '../src/payment-client';

const handlers = [
  http.get('https://api.payments.example.com/v1/charges/:chargeId', ({ params }) => {
    const { chargeId } = params;
    
    if (chargeId === 'ch_invalid') {
      return HttpResponse.json({ error: 'Charge not found' }, { status: 404 });
    }
    
    return HttpResponse.json({
      id: chargeId,
      amount: 4999,
      currency: 'usd',
      status: 'succeeded',
    }, { status: 200 });
  }),
];

const server = setupServer(...handlers);

describe('Payment Service Client', () => {
  beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
  afterEach(() => server.resetHandlers());
  afterAll(() => server.close());

  it('fetches payment status successfully for valid charge ID', async () => {
    const status = await fetchPaymentStatus('ch_9901');
    expect(status.amount).toEqual(4999);
    expect(status.status).toEqual('succeeded');
  });
});

この実行メカニズムを理解することで、MSWがNode.jsおよびTypeScriptのコードベースで優れている理由が明らかになります。MSWはテストランナーと同じJavaScriptメモリコンテキストを共有するため、スペックファイルはモックハンドラーの呼び出し引数を検査し、管理RESTコマンドを発行することなく、テストごとにモック実装をオーバーライドできます。

// Client code verified by MSW network interception
import axios from 'axios';

export async function fetchPaymentStatus(chargeId: string) {
  const response = await axios.get(`https://api.payments.example.com/v1/charges/${chargeId}`);
  return response.data;
}

逆に、WireMockはポリグロットなマイクロサービススタック全体で完全に言語に依存しません。単一のWireMockコンテナは、ローカルのDocker Composeスタック内でPython、Java、Go、およびNode.jsマイクロサービスに対して同時にモックエンドポイントを提供できます。

単純な静的JSONスタブを超えて、現代のマイクロサービスは、連続するAPI呼び出しがダウンストリームの応答値を変更するステートフルなインタラクションワークフローを必要とします。WireMockは、カスタムアプリケーションバックエンドロジックを必要とせずに、複数のHTTP呼び出しにわたるインタラクションの状態を追跡する組み込みのシナリオステートマシンを提供します。

// Stateful WireMock scenario mapping: Tracking state transition from UNPAID to PAID
{
  "scenarioName": "Order Payment Process",
  "requiredScenarioState": "Started",
  "newScenarioState": "Order Placed",
  "request": {
    "method": "POST",
    "url": "/api/v1/orders"
  },
  "response": {
    "status": 201,
    "jsonBody": { "orderId": "ord_505", "status": "PENDING" }
  }
}
Advertisement

MSWはNode.jsとブラウザでどのようにネットワークトラフィックをインターセプトしますか?

MSWは、ネイティブのglobalThis.fetchやhttp.requestオブジェクトを手動でオーバーライドするのではなく、低レベルのネットワークインターセプトフックを利用することで、ゼロレイテンシーのネットワークモックを実現します。Node.js環境では、MSWは@mswjs/interceptorsライブラリを使用してV8バインディングレイヤーでソケット接続をインターセプトします。

MSW Network Interception Engine

アプリケーションコードがAxios、ネイティブのfetch、またはnode-fetchを介してHTTPリクエストを発行すると、MSWのインターセプターは送信リクエストイベントをキャッチし、URLを登録されたリクエストハンドラーと比較し、HttpResponseオブジェクトを直接呼び出し元に返します。

// Advanced MSW 2.0 GraphQL and REST handler composition
import { http, graphql, HttpResponse } from 'msw';

export const microserviceHandlers = [
  // Intercept REST endpoint
  http.post('https://api.inventory.example.com/v1/stock/reserve', async ({ request }) => {
    const body = await request.json() as { sku: string; quantity: number };
    
    if (body.quantity > 100) {
      return HttpResponse.json({ error: 'Insufficient inventory' }, { status: 422 });
    }
    
    return HttpResponse.json({ reserved: true, reservationId: 'res_8812' });
  }),

  // Intercept GraphQL query
  graphql.query('GetCustomerDetails', ({ variables }) => {
    const { id } = variables;
    return HttpResponse.json({
      data: {
        customer: {
          id,
          name: 'Alex Mercer',
          email: 'alex.mercer@example.com',
        },
      },
    });
  }),
];

MSWはバージョン2.0で標準のFetch API RequestおよびResponseオブジェクトを使用するため、Node.jsユニットテスト用に記述されたモックハンドラーは、ブラウザ環境のService Workerで変更せずに再利用できます。

機能Mock Service Worker (MSW)WireMock
インターセプトモデルインプロセスV8ソケット / Service Worker独立したHTTP TCPプロキシサーバー
言語エコシステムNode.js, TypeScript, ブラウザJSポリグロット (JVM, Go, Python, Node, Ruby)
パフォーマンス即時 (<1msメモリリターン)低レイテンシー (5ms - 15ms TCPループ)
ステートフルシナリオサポートインメモリJS変数が必要組み込みJSONシナリオステートマシン
コンテナオーバーヘッドコンテナオーバーヘッドなしJVMまたはDockerコンテナが必要

この比較マトリックスは、MSWがNode.jsプロジェクトに対して優れた開発者エルゴノミクスと生の速度を提供する一方で、WireMockが多言語マイクロサービススタック全体でエンタープライズ規模の汎用性を提供することを示しています。

// Reusing MSW handlers between Node.js Vitest unit tests and Storybook UI components
import { http, HttpResponse } from 'msw';

export const commonUserHandlers = [
  http.get('/api/user', () => {
    return HttpResponse.json({ name: 'Jane Doe', role: 'admin' });
  }),
];

ユニットテスト、統合テスト、UIコンポーネントストーリー間でネットワークモック定義を共有することで、エンジニアリングチーム全体で重複するモックメンテナンス作業を排除できます。

RESTおよびGraphQLのサポートに加えて、最新のフロントエンドアプリケーションはリアルタイム通知のためにWebSocketに依存しています。MSW 2.0には、ws名前空間を介したWebSocketインターセプト機能が含まれており、開発者は双方向WebSocketイベントチャネルをクリーンにモックできます。

// MSW 2.0 WebSocket event mocking example
import { ws } from 'msw';

const chatService = ws.link('wss://chat.example.com/socket');

export const websocketHandlers = [
  chatService.addEventListener('connection', ({ client }) => {
    client.send(JSON.stringify({ type: 'WELCOME', message: 'Connected to mock server' }));

    client.addEventListener('message', (event) => {
      const data = JSON.parse(event.data as string);
      if (data.type === 'PING') {
        client.send(JSON.stringify({ type: 'PONG' }));
      }
    });
  }),
];

JVMおよびポリグロットスイート向けにWireMockスタンドアロンコンテナを設定する方法は?

WireMockスタンドアロンコンテナの設定には、リクエストマッチング基準と対応するモックHTTP応答テンプレートを定義するJSONマッピングファイルを宣言することが含まれます。WireMockは、受信HTTPリクエストメソッド、パスパターン、クエリパラメータ、およびヘッダー値を検査します。

WireMock Standalone Container Setup

WireMockは公式のDockerイメージ(wiremock/wiremock:3.5.0)を使用して実行でき、開発者はローカル開発環境と継続的インテグレーション環境で同一のモック設定を実行できます。

// WireMock JSON mapping file: mappings/get-user-profile.json
{
  "request": {
    "method": "GET",
    "urlPattern": "/api/v1/users/[0-9]+"
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "jsonBody": {
      "id": 101,
      "username": "developer_user",
      "tier": "enterprise",
      "status": "active"
    },
    "fixedDelayMilliseconds": 50
  }
}

JSONマッピングファイルをmappings/ディレクトリ内に配置すると、WireMockは手動のREST管理呼び出しを必要とせずに、起動時にスタブルートを自動的にロードするように指示されます。

# Docker Compose stack integrating WireMock for polyglot microservice integration tests
version: '3.8'

services:
  wiremock:
    image: wiremock/wiremock:3.5.0
    container_name: integration-wiremock
    ports:
      - "8080:8080"
    volumes:
      - ./wiremock/mappings:/home/wiremock/mappings
      - ./wiremock/__files:/home/wiremock/__files
    command: --verbose --enable-stub-cors

  order-service:
    build: .
    environment:
      USER_SERVICE_URL: "http://wiremock:8080"
    depends_on:
      - wiremock

Node.js統合テスト内でWireMockをプログラムで設定するには、wiremock-captainまたはネイティブのAxios管理REST APIクライアントを使用して、マッピングをリセットし、ランタイムで一時的な応答スタブを挿入できます。

// Programmatic WireMock stubbing via Node.js administrative REST API
import axios from 'axios';

export async function registerWireMockStub(wiremockUrl: string) {
  await axios.post(`${wiremockUrl}/__admin/mappings`, {
    request: {
      method: 'POST',
      url: '/api/v1/payments',
      bodyPatterns: [
        { matchesJsonPath: '$.amount' }
      ]
    },
    response: {
      status: 201,
      jsonBody: { transactionRef: 'tx_772199', status: 'approved' },
    },
  });
}

export async function resetWireMockMappings(wiremockUrl: string) {
  await axios.post(`${wiremockUrl}/__admin/mappings/reset`);
}

WireMockは、Handlebarsヘルパー関数を使用したランタイム応答テンプレートもサポートしています。この機能により、WireMockスタブはリクエストパラメータをエコーバックしたり、タイムスタンプを計算したり、応答ボディペイロードでランダムなUUIDを自動的に生成したりできます。

// WireMock Handlebars response templating mapping
{
  "request": {
    "method": "POST",
    "url": "/api/v1/echo"
  },
  "response": {
    "status": 200,
    "jsonBody": {
      "receivedAt": "{{now}}",
      "requestId": "{{randomValue type='UUID'}}",
      "clientName": "{{jsonPath request.body '$.name'}}"
    },
    "transformers": ["response-template"]
  }
}

ランタイムモックとフォールトインジェクションのパターンとは?

システムの回復力をテストするには、マイクロサービスが外部APIの障害、ネットワークタイムアウト、レート制限、破損したペイロード応答をどのように処理するかを検証する必要があります。MSWとWireMockの両方が、フォールトインジェクションとカオスエンジニアリングのシナリオをシミュレートするためのネイティブなプリミティブを提供します。

Fault Injection and Chaos Testing

MSWでは、ランタイムのフォールトインジェクションは、個々のスペックファイル内の1回限りのネットワークハンドラーオーバーライド(server.use())を使用して行われます。このパターンは、スイート内の他のテストケースを汚染することなく、単一のテストアサーションのモック応答を変更します。

// Example: MSW runtime fault injection simulating 503 service unavailable and network delays
import { setupServer } from 'msw/node';
import { http, HttpResponse, delay } from 'msw';
import { microserviceHandlers } from './handlers';
import { fetchBillingDetails } from '../src/billing-client';

const server = setupServer(...microserviceHandlers);

describe('Billing Service Fault Resilience', () => {
  beforeAll(() => server.listen());
  afterEach(() => server.resetHandlers());
  afterAll(() => server.close());

  it('handles 503 Service Unavailable gracefully with exponential backoff', async () => {
    // Inject temporary 503 error for billing endpoint
    server.use(
      http.get('https://api.billing.example.com/v1/invoices', () => {
        return new HttpResponse(null, { status: 503, statusText: 'Service Unavailable' });
      })
    );

    await expect(fetchBillingDetails()).rejects.toThrow('Billing service unavailable');
  });

  it('handles high network latency timeout scenarios', async () => {
    // Inject 3000ms delay to trigger client HTTP timeout threshold
    server.use(
      http.get('https://api.billing.example.com/v1/invoices', async () => {
        await delay(3000);
        return HttpResponse.json({ invoices: [] });
      })
    );

    await expect(fetchBillingDetails({ timeoutMs: 1000 })).rejects.toThrow('Request timed out');
  });
});

WireMockは、fault応答プロパティを介してフォールトインジェクションをサポートしており、ガベージデータの返却(GARBAGE_KEYS)、接続の切断(DROP_CHUNK)、または接続の早期終了(EMPTY_RESPONSE)などの特定のネットワークレベルのエラーシナリオをサポートしています。

// WireMock fault injection mapping: Connection reset simulation
{
  "request": {
    "method": "POST",
    "url": "/api/v1/orders"
  },
  "response": {
    "fault": "CONNECTION_RESET_BY_PEER"
  }
}

継続的インテグレーション実行中にネットワーク障害モードをシミュレートすることで、ダウンストリームの依存関係が停止した場合に、マイクロサービスの再試行ループ、サーキットブレーカー、およびフォールバックキャッシュハンドラーが確実に動作することを確認できます。

// Custom circuit breaker implementation verified against MSW fault injection
import axios from 'axios';

export class ResilientApiClient {
  private failureCount = 0;
  private readonly threshold = 3;

  async executeRequest(url: string) {
    if (this.failureCount >= this.threshold) {
      throw new Error('Circuit breaker open: requests blocked');
    }

    try {
      const response = await axios.get(url);
      this.failureCount = 0;
      return response.data;
    } catch (error) {
      this.failureCount++;
      throw error;
    }
  }
}

カオステストパターンは、MSWハンドラー内でランダム化されたフォールトインジェクションレートを設定することで自動化できます。10%の確率で500ステータス応答を導入することで、バックエンドクライアントライブラリがデータを失うことなく一時的なネットワークグリッチを処理することを確認します。

// Randomized chaos injection middleware in MSW
export function createChaosHandler(targetUrl: string, errorProbability = 0.1) {
  return http.get(targetUrl, () => {
    if (Math.random() < errorProbability) {
      return new HttpResponse(null, { status: 500, statusText: 'Internal Chaos Error' });
    }
    return HttpResponse.json({ status: 'ok' });
  });
}
Advertisement

どのモックフレームワークがマイクロサービスアーキテクチャに適していますか?

MSWとWireMockのどちらを選択するかは、チームのプログラミング言語の多様性、テストランナーインフラストラクチャ、およびアーキテクチャ要件によって異なります。どちらのツールも、不安定な外部依存関係からテストスイートを分離するための強力な機能を提供します。

エンジニアリング組織が主にJavaScriptとTypeScriptで構築している場合、MSWは最速の実行速度、最もクリーンな開発者エルゴノミクス、およびNode.jsユニットテストとフロントエンドブラウザコンポーネントテスト間でのモックハンドラーのネイティブ共有を提供します。

組織がJava、Python、Go、C#にまたがるポリグロットなマイクロサービス環境を運用している場合、Docker ComposeとKubernetes内に集中型WireMockコンテナスタックをデプロイすることで、すべてのチームのコードベースで一貫したモックエンドポイントを提供できます。

// Calculation helper for evaluating mock execution latency overhead
function estimateSuiteRunTime(
  totalSpecs: number,
  avgNetworkLatencyMs: number,
  parallelWorkers: number
): number {
  const totalLatencyMs = (totalSpecs * avgNetworkLatencyMs) / parallelWorkers;
  return Math.round(totalLatencyMs / 1000);
}

// MSW in-memory vs WireMock socket latency comparison
const mswSuiteTime = estimateSuiteRunTime(500, 0.5, 4);   // ~1 second
const wiremockSuiteTime = estimateSuiteRunTime(500, 12, 4); // ~15 seconds

console.log(`Estimated 500-spec run time with MSW: ${mswSuiteTime}s`);
console.log(`Estimated 500-spec run time with WireMock: ${wiremockSuiteTime}s`);

高速なローカルNode.jsユニットテストにMSWを、マルチコンテナ統合テストにWireMockを組み合わせることで、最適なテストアーキテクチャが実現します。信頼性の高いモック戦略を導入することで、エンジニアリングチームはシステムの信頼性に完全に自信を持ってマイクロサービスアップデートをリリースできます。

エンジニアリング組織全体で明確なモックガイドラインを確立することで、テストスイートの経年劣化を防ぎます。モックハンドラーパターンを文書化し、ネットワークインターセプトカバレッジを監査し、モックスタブを本番API仕様と整合させることで、統合テストの正確性と保守性を維持します。

ライブステージングAPI仕様に対してモックハンドラーの忠実性を定期的に監査することで、モックドリフトを防ぎ、シミュレートされた応答が実際のバックエンドスキーマの変更を反映していることを保証します。チームは、MSWおよびWireMockスタブと並行してOpenAPI契約検証ツールを自動化し、モックの正確性を保証できます。

開発チーム間で共有モックリポジトリを確立することで、重複するモック定義を最小限に抑えます。APIスキーマが変更された場合、中央のモックライブラリを更新すると、すべてのコンシューミングマイクロサービスで統合テストの期待値が自動的に更新されます。

ネットワークモックフレームワーク導入前後のチーム開発者速度を評価することで、大幅な生産性向上を浮き彫りにします。ステージングAPIの可用性への依存を排除することで、ソフトウェアエンジニアは複雑な機能を完全にオフラインで開発および検証できます。

完全なモックインフラストラクチャへの投資は、マイクロサービス境界全体で長期的なエンジニアリングの信頼を構築します。チームは、すべての本番リリースで高いソフトウェア品質を維持しながら、迅速に革新できます。

継続的インテグレーションパイプライン内で自動化されたモック検証チェックを設定することで、モックスタブが本番エンドポイント定義と同期した状態を維持することを保証します。エンジニアリングチームは、ダウンストリームの破壊的変更のリスクを冒すことなく、マイクロサービスを迅速に構築およびテストできます。

こちらもおすすめです

よくある質問

MSWとWireMockの主な違いは何ですか?

MSWは、Node.js V8ソケットまたはブラウザService Workerレイヤーでインプロセスでネットワークリクエストをインターセプトし、ネットワークオーバーヘッドなしでモック応答を返します。WireMockは、JVMまたはDockerコンテナで実行される独立したHTTPプロキシサーバーとして動作し、クライアントアプリケーションは実際のTCPソケットを介して接続します。

MSW 2.0は以前のバージョンとどう異なりますか?

MSW 2.0は、標準のFetch APIプリミティブ(Request、Response、HttpResponse)を採用し、ハンドラー構文をhttp.get()およびhttp.post()に更新しています。カスタム応答リゾルバーの抽象化を削除し、モックハンドラーがNode.jsと最新のブラウザで同じように実行できるようにしています。

WireMockはJava以外のプロジェクトでも使用できますか?

はい、WireMockはスタンドアロンのDockerコンテナまたはJVMプロセスとして実行できるため、完全に言語に依存しません。Python、Go、Node.js、およびRubyのマイクロサービスは、WireMockホストポートにHTTP呼び出しを行ったり、WireMockのREST管理APIを介してプログラムでマッピングを設定したりできます。

MSWでネットワーク遅延をシミュレートするにはどうすればよいですか?

MSWでネットワーク遅延をシミュレートするには、モックリクエストハンドラー(await delay(1000))内でdelay()ヘルパー関数を使用できます。これにより、クライアントアプリケーションが低速な応答とHTTPタイムアウトしきい値をどのように処理するかをテストできます。

WireMockのフォールトインジェクションとは何ですか?

WireMockのフォールトインジェクションは、不正なデータの返却(GARBAGE_KEYS)、接続の切断(DROP_CHUNK)、またはTCPソケットのリセット(CONNECTION_RESET_BY_PEER)など、低レベルのネットワーク障害モードをシミュレートすることを可能にし、サーキットブレーカーのカオスエンジニアリングテストを可能にします。

MSWはGraphQL APIをモックできますか?

はい、MSWにはファーストクラスのGraphQLインターセプトサポート(graphql.query()およびgraphql.mutation())が含まれています。MSWはGraphQLの操作名とリクエストペイロードを検査し、モックされたJSONデータ構造をシームレスに返します。

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement