TestHostを用いたASP.NET Coreアプリケーションのテスト

# やること

Microsoft.AspNetCore.TestHostを使用して、ASP.NET Core Webアプリケーションをオンメモリにホストした状態でのテストを行います。

TestHostを用いることで、HTTPリクエストをエミュレートしたエンドツーエンドの試験が可能になります。

要は、Javaで埋め込みコンテナを使ってテストする～、みたいな奴のASP.NET Core版です。

# 環境

* Visual Strudio 2015
* .NET Core Tooling Preview 2 for Visual Studio 2015

# 前提

テスト対象となるASP.NET Core Webアプリケーションのプロジェクトが作成され、.NET Core 1.1へアップデート済みの状態とします。

また、今回はわかりやすいようにAPIコントローラーの呼び出しをテスト対象とするため、APIコントローラーを追加済みの状態とします。

# サンプル

以下に完成形のサンプルを用意しています。

* [TestHostExample](https://github.com/usausa/Example-Net-AspNetCore/tree/master/TestHostExample)

# 手順

テストプロジェクトの構築手順について記述します。

ASP.NET Coreアプリケーションのソリューションにテストプロジェクトとして「Class Library (.NET Core)」のプロジェクトを追加します。

作成されたプロジェクトはターゲットフレームワークが.NET Standardになっているため、そのままではASP.NET Coreアプリケーションのプロジェクトを参照できないので`project.json`の内容を変更します。

ターゲットフレームワークの変更と、必要なライブラリの参照を追加した`project.json`は以下の様になります。

```javascript
{
  "version": "1.0.0-*",

  "testRunner": "xunit",

  "dependencies": {
    "Microsoft.NETCore.App": {
      "type": "platform",
      "version": "1.1.0"
    },
    "dotnet-test-xunit": "2.2.0-preview2-build1029",
    "xunit": "2.2.0-beta4-build3444",
    "Microsoft.AspNetCore.TestHost": "1.1.0",
    "Microsoft.DotNet.InternalAbstractions": "1.0.0",
    "TestHostExample": "1.0.0-*"
  },

  "frameworks": {
    "netcoreapp1.1": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  }
}
```

行っている作業は以下になります。

- .NETStandard 1.6の設定から.NETCoreApp 1.1用にdependenciesとframeworksを変更
- testRunnerセクション追加
- dotnet-test-xunit、xunitへの参照追加
- Microsoft.AspNetCore.TestHostへの追加
- Microsoft.DotNet.InternalAbstractionsへの追加
- Webアプリケーションプロジェクト(TestHostExample)への参照の追加

なお、Microsoft.DotNet.InternalAbstractionsについては、1.0環境では問題ありませんが、Microsoft.AspNetCore.Mvcを1.1.0にあげると問題が発生する(Visual Studioのテストエクスプローラーにテストが表示されない、`dotnet test`コマンドによるテストでは例外が発生する)ため追加しています。

# テストコード

テストコードは以下の内容となります。

```csharp
using System.Net.Http;

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;

using Xunit;

public class ValuesControllerTest
{
    [Fact]
    public async void GetReturnValueArray()
    {
        var config = new ConfigurationBuilder().Build();

        var host = new WebHostBuilder()
            .UseConfiguration(config)
            .UseStartup<Startup>();

        var server = new TestServer(host);

        using (var client = server.CreateClient())
        {
            var request = new HttpRequestMessage(HttpMethod.Get, "/api/values/");
            var response = await client.SendAsync(request);

            var content = await response.Content.ReadAsStringAsync();

            Assert.Equal(content, "[\"value1\",\"value2\"]");
        }
    }
}
```

テスト対象プロジェクトのStartupをホストしてTestServerのインスタンスを作成し、そこからクライアントの作成を行って、HTTPリクエスト/レスポンスのエミュレーションによるテスト内容になります。

テスト用のカスタマイズを行う場合、TestServerのインスタンス作成前にconfigやWebHostBuilderをいじったりします。

あとは、Visual Srudioのテストメニューや`dotnet test`コマンドによりテストを実行するだけです。

ホスティングまで疑似ってテストすると重いと思うかもしれませんが、テストの実行時間は1個目こそ500msくらいかかりますが、それ以降は10msくらいで終わるので気にしなくていいと思います。

# うさコメ

ところで、Startup内でServiceProviderに登録しているコンポーネントについて、テスト用のモックへの置き換えはどこでやるのがええんやろね(・ω・)？

Startupのコンポーネント登録箇所をvirtualにしてそこで、とか？