ASP.NET_Core

ASP.NET Core MVC 2.0 時点での xUnit と Microsoft.AspNetCore.TestHost を利用した Integration Test で気をつける点

ここ数年 C# からは離れていたのですが久しぶりに触ることになったのでテスト周りがどうなっているのか調べている途中で TestHost に出会いました。

Microsoft.AspNetCore.TestHost を使った Integration Test に関して

ASP.NET Core でのテストの統合 | Microsoft Docs

自動翻訳で微妙に読みにくいですが、上記が一つのポインタになると思います。

RSpec だと request spec 辺りになるかと思います。feature spec のようなブラウザにレンダリングさせての End-to-End テストとは異なりますね。

Web API を作っているケースで使えそうという感じです。

最も簡単なテスト

これは上に貼ったリンクのサンプルです。

以下の Startup はテスト対象の ASP.NET Core アプリケーション側のクラスです。WebHost としてビルドしたものを TestServer に渡しているだけですね。

後は、そこから作成できるクライアントでテストを書いていけます。

public class PrimeWebDefaultRequestShould
{
    private readonly TestServer _server;
    private readonly HttpClient _client;
    public PrimeWebDefaultRequestShould()
    {
        // Arrange
        _server = new TestServer(new WebHostBuilder()
            .UseStartup<Startup>());
        _client = _server.CreateClient();
    }

    [Fact]
    public async Task ReturnHelloWorld()
    {
        // Act
        var response = await _client.GetAsync("/");
        response.EnsureSuccessStatusCode();

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

        // Assert
        Assert.Equal("Hello World!",
            responseString);
    }
}

MVC の場合

上記のドキュメントの中盤の「Mvc Razor/テストの統合」にもあるように以下の設定をテスト側の csproj に追加しなくてはいけません。

HogeTest.csproj
<PropertyGroup>
  <PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>

ただ、これだけだと動作しません。

上記のようにいろいろと workaround があるようですが、たいてい以下のことをやっています。

  • shadowCopy: false を設定した xunit.runner.json を配置
  • deps.json をコピーするビルドタスクを追加
HogeTest.csproj
  <Target Name="CopyAditionalFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">
    <ItemGroup>
      <DepsFilePaths Include="$([System.IO.Path]::ChangeExtension('%(_ResolvedProjectReferencePaths.FullPath)', '.deps.json'))" />
    </ItemGroup>

    <Copy SourceFiles="%(DepsFilePaths.FullPath)" DestinationFolder="$(OutputPath)" Condition="Exists('%(DepsFilePaths.FullPath)')" />
  </Target>
  • テスト対象のプロジェクトを ContentRoot とするように UseContentRoot を指定
var asm = typeof(Startup).Assembly.GetName().Name;
string appRootPath = Path.GetFullPath(Path.Combine(
    AppContext.BaseDirectory,
    "..", "..", "..", "..", asm));

_server = new TestServer(new WebHostBuilder()
    .UseContentRoot(appRootPath) // 💡 これを追加
    .UseStartup<Startup>());

2.1 でその辺りがよくなる

Improved end to end testing support for MVC applications · Issue #275 · aspnet/Announcements

ここでケアされるのは以下のようです。

  • It copies the .deps file from your project into the test assembly bin folder.
  • It sets the content root the application's project root so that static files and views can be found.
  • It provides a class WebApplicationTestFixture that streamlines the bootstrapping of your app on TestServer.

上記には無い xunit でのシャドウコピーに関しては引き続き行う必要があると書いてありますね。

内容については以下らへんで実際に見ることができます。(すでに preview も出ているので試すこともできるのかな)

https://github.com/aspnet/Mvc/tree/dev/src/Microsoft.AspNetCore.Mvc.Testing

WebApplicationTestFixture の実装や、deps.jsonのコピーに関する build target ファイルもありますね。

上記に加えて MVC 側から MvcTestFixture が提供されるようです。

https://github.com/aspnet/Mvc/blob/dev/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs

サンプル

以下に今回検証したサンプルを置いておきます。

(ホントは E2E テストじゃないんですが、最初は E2E テストが書きたくて試し始めたので名が体を表してないのは許してください)

https://github.com/dany1468/aspnetcore2_0_TestHost_sample

E2eTestSample は Visual Studio for Mac で MVC プロジェクトを作ってそのままです。

E2eTest は以下のようになっています。

  • TestUsingWebHostBuilderDirectly.cs
    • workaround の実装の通りに UseContentRoot を使ったもの
  • TestUsingInfrastracture.cs
    • infrastructure/ にコピーしてきた、2.1 で追加されるものを利用して書き直したもの

infrastructure ディレクトリのファイルは、dev ブランチから今回のお試しに必要なものを取ってきただけです。WebHostBuilderExtensions は以下の aspnet/Hosting の一部です。

https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs

その他 ASP.NET Core 2.0 での Integration Test の参考になりそうなもの