概要
- WireMock.NetというHTTP APIの動作を模倣するツールを紹介する。
- WireMock.Netを利用した単体テストの簡単なサンプルを示す。
- WireMock.Netを利用したコンソールアプリの簡単なサンプルを示す。
- 最後に、参考にした情報をまとめる。
WireMock.Netとは?
HTTP APIの動作を簡単に模倣できるツールである。
元々はJavaで利用可能なモックツールWireMockがあり、これを.NET用にC#で模倣したものである。
実行可能な環境はローカル、IIS、Azure、Dockerなど多岐に渡る。
下記ような場面で活用が可能であると考える。
(1)ユニットテストや結合テスト
-
HTTP通信を模倣できるため、HTTP通信を行うクラスの単体テストが可能になる。
- HTTP通信を単体テストする方法として、下記リンクのようにMoqを用いてHttpMessageHandlerをモック化する手法がある。
この手法に比べ、WireMock.Netは機能が豊富なため、評価できることが多い。テストコードの実装もシンプルに記述できる。
HttpClientをMoqでモック化する
- HTTP通信を単体テストする方法として、下記リンクのようにMoqを用いてHttpMessageHandlerをモック化する手法がある。
-
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]をインストールする。
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要求を送信する。
使い方はこちらを参考にしてください。
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を紹介したブログ。 |