8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

.NET環境で使えるHTTP API動作を模倣するツールWireMock.Netを紹介する

Last updated at Posted at 2021-08-05

概要

  • WireMock.NetというHTTP APIの動作を模倣するツールを紹介する。
  • WireMock.Netを利用した単体テストの簡単なサンプルを示す。
  • WireMock.Netを利用したコンソールアプリの簡単なサンプルを示す。
  • 最後に、参考にした情報をまとめる。

WireMock.Netとは?

HTTP APIの動作を簡単に模倣できるツールである。
元々はJavaで利用可能なモックツールWireMockがあり、これを.NET用にC#で模倣したものである。
実行可能な環境はローカル、IIS、Azure、Dockerなど多岐に渡る。

参考:What Is WireMock.Net

下記ような場面で活用が可能であると考える。

(1)ユニットテストや結合テスト

  • HTTP通信を模倣できるため、HTTP通信を行うクラスの単体テストが可能になる。

    • HTTP通信を単体テストする方法として、下記リンクのようにMoqを用いてHttpMessageHandlerをモック化する手法がある。
      この手法に比べ、WireMock.Netは機能が豊富なため、評価できることが多い。テストコードの実装もシンプルに記述できる。
      HttpClientをMoqでモック化する
  • HTTP応答の内容を自由に設定できるため、HTTP応答に応じた画面遷移の評価などが容易になる。
    個人的には、通信遅延が発生したケース、異常なステータス(HTTP 4xx または 5xx)になったケースの評価を楽にできる点が素晴らしいと思っています。

(2)HTTP APIのサーバが未実装な場合
HTTP API仕様書に基づき、モックサーバをWireMockで作ることにより、サーバの実装完了を待たずに、クライアントの実装を進められる。

基本的な使い方

サーバーを起動する方法

using WireMock.Server;

var server = WireMockServer.Start(int? port = 0, bool ssl = false);

引数として、ポート番号(port)・SSLを使用するか(ssl)を指定できる。本記事ではSSLを使用しません。
ポート番号を指定しない場合は、localhostに割り当てられたランダムなポート番号でサーバーが起動する。
ポート番号を指定した場合は、指定したポート番号で起動する。

指定したポート番号がOSが使用している場合、実行時エラーになります。

HTTP要求とHTTP応答をマッピングする方法

HTTP要求・HTTP応答の組み合わせを登録する必要がある。

下記のWeb APIを想定した場合の実装例を示す。

HttPメソッド URL  応答ステータス   応答Body  ヘッダー情報   補足
GET http://localhost:xxxxx/foo 200 {"msg":"Hello world!"} Content-Type: application/json xxxxxはport番号。
応答に1秒かかる。
var server = WireMockServer.Start();
const string expected = @"{ ""msg"": ""Hello world!"" }";
server 
    .Given(Request.Create().WithPath("/foo").UsingGet())
    .RespondWith(
        Response.Create()
            .WithStatusCode(HttpStatusCode.OK)
            .WithHeader("Content-Type", "application/json")
            .WithBody(expected)
            .WithDelay(TimeSpan.FromSeconds(1)));

Given()の引数にHTTP要求情報(URL/HTTP メソッドなど)を記述する。
RespondWith()の引数に、HTTP要求に対応する応答情報(応答ステータス/ヘッダー/ボディー/通信遅延など)を記述する。

サーバーを終了する方法

serve.Stop();

実行環境

実行環境

フレームワーク
 .NET Framework 4.6.2
Nugetパッケージ
 WireMock.NET 1.4.19
 NUnit 3.12.0
 NUnit3TestAdapter 3.16.1
 Microsoft.NET.Test.Sdk 16.5.0
 

WireMock.Netを用いた単体テスト

実際にWireMock.Netを利用して、単体テストの簡単なサンプルを実行する。

1.テストプロジェクトを作成する

[NUnit3 テストプロジェクト]->ターゲットフレームワークを選択する
今回は.NET Framework4.6.2で作成。

2.WireMock.NETを追加する

[Nugetパッケージの管理]を起動する。[WireMock.Net]をインストールする。
image.png

3.テストコードを作成する

テストコード

using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using NUnit.Framework;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;

namespace TestWireMock
{
    public class Tests
    {
        private WireMockServer _mockServer;
        private static HttpClient _client;

        [SetUp]
        public void Setup()
        {
            _mockServer = WireMockServer.Start();
            _client = new HttpClient();
        }

        [Test]
        public async Task MockHttpApi_Foo_CaseStatusOk()
        {
            // Arrange WireMock.Net serverの設定
            const string expected = @"{ ""msg"": ""Hello world!"" }";
            _mockServer
                .Given(Request.Create().WithPath("/foo").UsingGet())
                .RespondWith(
                    Response.Create()
                        .WithStatusCode(HttpStatusCode.OK)
                        .WithHeader("Content-Type", "application/json")
                        .WithBody(expected));

            var port = _mockServer.Ports;
            // Act HTTPサーバーのURLにアクセスして、HTTP応答を取得する
            var response = await _client.GetAsync($"{_mockServer.Urls[0]}/foo");
            var result = await response.Content.ReadAsStringAsync();

            //Assert
            Assert.AreEqual(expected, result);
        }

        [TearDown]
        public void ShutdownServer()
        {
            _mockServer.Stop();
        }
    }
}

テストコードを記載する上での注意点

本項目の記載は、Using WireMock in UnitTestsの記述を意訳したものです。

(1)ハードコードでポートを指定しないこと

ハードコードで指定したポートがOS上で空いていることが100%保証されていないため、ユニットテストを実行する際に問題を引き起こす可能性がある。
WireMock.Netにポートを動的に選択させること。

なお、WireMock.Netからポート番号は下記で取得できる。
WireMockServer.Ports or WireMockServer.Urls

(2)テスト終了時に後処理を行うこと

各テストの終了時には、リクエストログをクリーンアップするか、サーバをシャットダウンする。

4.単体テストを実行する

テストを実行して、[Failed]にならないことを確認する。

WireMock.Netを用いたコンソールアプリケーション

ソースコード

using System;
using System.Net;
using WireMock.Exceptions;
using WireMock.Logging;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;

namespace WireMockStandAlone
{
    class Program
    {
        static void Main( string[] args )
        {
            // WireMockServerは、IDisposableを継承している。リソース解放の構文としてusingを使用できるが、try-finallyで記述する。
            // 理由は、終了処理に非同期処理が含まれているためである。STOP()は非同期処理の完了を待つが、Dispose()は完了を待たない。
            WireMockServer server = null;
            try
            {
                var port = 8080;
                server = WireMockServer.Start(
                    new WireMockServerSettings()
                    {
                        Port = port,
                        Logger = new WireMockConsoleLogger()
                    });
                Console.WriteLine($"{nameof(WireMockServer)} Running at {nameof(port)} : {port}");
                const string expected = @"{ ""msg"": ""Hello world!"" }";
                server 
                    .Given(Request.Create().WithPath("/foo").UsingGet())
                    .RespondWith(
                        Response.Create()
                            .WithStatusCode(HttpStatusCode.OK)
                            .WithHeader("Content-Type", "application/json")
                            .WithBody(expected)
                            .WithDelay(TimeSpan.FromSeconds(1)));

                Console.WriteLine("Press any key to stop...");
                Console.ReadKey(true);
            }
            catch (WireMockException e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                server?.Stop();
            }
        }
    }
}

実行結果

起動時のコンソール出力

起動時のコンソール出力
2022/02/11 11:02:48 [Info] : By Stef Heyenrath (https://github.com/WireMock-Net/WireMock.Net)
2022/02/11 11:02:48 [Debug] : Server settings {
  "Port": 8080,
  "UseSSL": null,
  "StartAdminInterface": null,
  "ReadStaticMappings": null,
  "WatchStaticMappings": null,
  "WatchStaticMappingsInSubdirectories": null,
  "ProxyAndRecordSettings": null,
  "Urls": null,
  "StartTimeout": 10000,
  "AllowPartialMapping": null,
  "AdminUsername": null,
  "AdminPassword": null,
  "AdminAzureADTenant": null,
  "AdminAzureADAudience": null,
  "RequestLogExpirationDuration": null,
  "MaxRequestLogCount": null,
  "CorsPolicyOptions": null,
  "AllowCSharpCodeMatcher": null,
  "AllowBodyForAllHttpMethods": null,
  "AllowOnlyDefinedHttpStatusCodeInResponse": null,
  "DisableJsonBodyParsing": null,
  "DisableRequestBodyDecompressing": null,
  "HandleRequestsSynchronously": null,
  "ThrowExceptionWhenMatcherFails": null,
  "CertificateSettings": null,
  "CustomCertificateDefined": false,
  "WebhookSettings": null,
  "UseRegexExtended": true,
  "SaveUnmatchedRequests": null
}
2022/02/11 11:02:48 [Info] : Server using .NET Framework 4.6.1 or higher
WireMockServer Running at port : 8080
Press any key to stop...

HTTP要求を受信した時のコンソール出力

VSCodeのREST Clientを用いて、http要求を送信する。
使い方はこちらを参考にしてください。

test.http
GET http://localhost:8080/foo HTTP/1.1
Content-Type: application/json
HTTP要求を受信した時のコンソール出力
2022/02/11 11:06:36 [DebugRequestResponse] : Admin[False] {
  "Guid": "9639e3e0-b4e7-428f-9017-46bb072f9f15",
  "Request": {
    "ClientIP": "127.0.0.1",
    "DateTime": "2022-02-11T11:06:35.4409977Z",
    "Path": "/foo",
    "AbsolutePath": "/foo",
    "Url": "http://localhost:8080/foo",
    "AbsoluteUrl": "http://localhost:8080/foo",
    "ProxyUrl": null,
    "Query": {},
    "Method": "GET",
    "Headers": {
      "Connection": [
        "close"
      ],
      "Content-Type": [
        "application/xml"
      ],
      "Accept-Encoding": [
        "gzip, deflate"
      ],
      "Host": [
        "localhost:8080"
      ],
      "User-Agent": [
        "vscode-restclient"
      ]
    },
    "Cookies": null,
    "Body": null,
    "BodyAsJson": null,
    "BodyAsBytes": null,
    "BodyEncoding": null,
    "DetectedBodyType": null,
    "DetectedBodyTypeFromContentType": null
  },
  "Response": {
    "StatusCode": 200,
    "Headers": {
      "Content-Type": [
        "application/json"
      ]
    },
    "BodyDestination": "SameAsSource",
    "Body": "{ \"msg\": \"Hello world!\" }",
    "BodyAsJson": null,
    "BodyAsBytes": null,
    "BodyAsFile": null,
    "BodyAsFileIsCached": null,
    "BodyOriginal": null,
    "BodyEncoding": {
      "CodePage": 65001,
      "EncodingName": "Unicode (UTF-8)",
      "WebName": "utf-8"
    },
    "DetectedBodyType": 1,
    "DetectedBodyTypeFromContentType": 0,
    "FaultType": null,
    "FaultPercentage": null
  },
  "MappingGuid": "a6976d50-0dc5-449d-81b8-70b36f865564",
  "MappingTitle": null,
  "RequestMatchResult": {
    "TotalScore": 2.0,
    "TotalNumber": 2,
    "IsPerfectMatch": true,
    "AverageTotalScore": 1.0,
    "MatchDetails": [
      {
        "Name": "PathMatcher",
        "Score": 1.0
      },
      {
        "Name": "MethodMatcher",
        "Score": 1.0
      }
    ]
  },
  "PartialMappingGuid": "a6976d50-0dc5-449d-81b8-70b36f865564",
  "PartialMappingTitle": null,
  "PartialRequestMatchResult": {
    "TotalScore": 2.0,
    "TotalNumber": 2,
    "IsPerfectMatch": true,
    "AverageTotalScore": 1.0,
    "MatchDetails": [
      {
        "Name": "PathMatcher",
        "Score": 1.0
      },
      {
        "Name": "MethodMatcher",
        "Score": 1.0
      }
    ]
  }
}

あとがき

WireMock.Netの使い方を学ぶには、下記Wikiが参考になります。
WireMock.Net Wiki

参考

No リンク 備考
1 WireMock WireMockの公式ページ
2 WireMock.Net GitHub WireMock.Netのページ。概要・機能・関連ページへのリンクがある。
3 What Is WireMock.Net WireMock Wiki Page:What Is WireMock.Net
4 WireMock.Net Wiki WireMock Wiki Page:Home
5 Using WireMock in UnitTests WireMock.Netの単体テストのページ
WireMockServerのテストコードも参考になる。
6 Writing robust integration tests in .NET with WireMock.NET YoutubeでWireMock.Netを紹介してる動画(英語)
7 WireMock.Net for better Integration Tests WireMock.Netを紹介したブログ。
8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?