【ASP.NET Core】単体テストってみる -導入と Controller のテスト
はじめに
これは C# Advent Calendar 2018 十七日目の穴埋め記事です。
前回 Azure Pipeline で Unity プロジェクトのビルドをしようとしたらちょっと厳しそう (少なくとも現状では)、という結論になったわけですが、まずは Azure Pipeline 力をゲットするやで!ということで、 ASP.NET Core のビルドに挑戦してみることにしました。
で、CI でビルド + マージしていくとなれば、テストが欲しいところ。
ということで、今回は xUnit と Moq を使ったテストに挑戦してみます。
どこからテストするか?というところで迷ったのですが、まずは Controller クラスから試してみることにします。
環境
- .NET Core ver.2.2.101
- xUnit ver.2.4.1
- Moq ver.4.10.1
- Rider ver.2018.2.3
テスト対象のクラス
過去の記事で書いたコードをもとにこんなクラスにしてみました。
HomeController.cs
using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using AspNetCoreTestSample.FileLoaders; using AspNetCoreTestSample.Models; using Microsoft.AspNetCore.Mvc; namespace AspNetCoreTestSample.Controllers { public class HomeController: Controller { private readonly ILocalFileLoader _fileLoader; public HomeController(ILocalFileLoader fileLoader) { _fileLoader = fileLoader; } [Route("")] public IActionResult Index() { ViewData["SampleUsers"] = Enumerable.Range(0, 5) .Select(n => "User " + n) .ToList(); return View("/Views/Index.cshtml"); } [Route("/items/update")] [HttpPost] [Produces("application/json")] public async Task< List< string>> ReloadAsync() { await _fileLoader.ReloadFilePathsAsync(); return _fileLoader.FilePaths; } } }
ILocalFileLoader.cs
using System.Collections.Generic; using System.Threading.Tasks; namespace AspNetCoreTestSample.FileLoaders { public interface ILocalFileLoader { List< string> FilePaths { get; } Task ReloadFilePathsAsync(); } }
準備
テスト用のプロジェクトを追加する
テスト用にプロジェクト( Unit Test Project )を Type を xUnit にして追加します。
名前は テスト対象のプロジェクト名.Tests が良いらしいです。
プロジェクトが出来上がったら、 NuGet で Moq をテストプロジェクトにインストールします。
テストプロジェクト上で右クリック > Add > Add Reference... で、テスト対象のプロジェクトへの参照を追加します。
なお、この参照設定を行うため、 NuGet でパッケージをインストールする時はどちらか一方にのみインストールする必要があります(重複によるエラーを避けるため)。
NuGet でさらにパッケージをインストール
プロジェクトの作成自体は完了なのですが、実際に下記を書いてみるとエラーになります。
HomeControllerTest.cs
using System; using AspNetCoreTestSample.Controllers; using AspNetCoreTestSample.FileLoaders; using Moq; using Xunit; namespace AspNetCoreTestSample.Tests.Controllers { public class HomeControllerTest { private HomeController _controller; public HomeControllerTest() { Mock< ILocalFileLoader> mock = new Mock< ILocalFileLoader>(); _controller = new HomeController(mock.Object); } [Fact] public void Sample() { Assert.Empty(""); } } }
エラーの内容はこちら。
System.IO.FileNotFoundException : Could not load file or assembly 'Microsoft.AspNetCore.Mvc.ViewFeatures, Version=2.2.0.0, Culture=neutral, PublicKeyToken=example'. 指定されたファイルが見つかりません。
で、これを解決するには、テストプロジェクト側に NuGet で下記を追加インストールする必要があるようです。
- Microsoft.AspNetCore.Mvc.Core
- Microsoft.AspNetCore.Mvc.Abstractions
- Microsoft.AspNetCore.Mvc.ViewFeatures
テストを書く
では準備も揃ったところで、いくつかテストを書いてみることにします。
HomeControllerTest.cs
using System; using System.Collections.Generic; using AspNetCoreTestSample.Controllers; using AspNetCoreTestSample.FileLoaders; using Microsoft.AspNetCore.Mvc; using Moq; using Xunit; namespace AspNetCoreTestSample.Tests.Controllers { public class HomeControllerTest: IDisposable { private readonly HomeController _controller; public HomeControllerTest() { // 初期化処理. Mock< ILocalFileLoader> mock = new Mock< ILocalFileLoader>(); // ILocalFileLoader.FilePaths が呼ばれたときに Returns の中身を返す. mock.Setup(m => m.FilePaths).Returns(new List< string> {"hello", "world"}); _controller = new HomeController(mock.Object); } public void Dispose() { // 完了後にアンマネージドリソースの処理したり. Console.WriteLine("disposed"); } [Fact] public void Index_OpenAndGetType() { Assert.IsType< ViewResult>(_controller.Index()); } [Fact] public async void Reload_GetTwoItems() { List< string> result = await _controller.ReloadAsync(); Assert.NotNull(result); Assert.True(result.Count == 2); } } }
- コメントにも書きましたが、アンマネージドリソースの後処理が必要な場合など、 IDispose を継承すると Dispose が呼ばれるようになります。
結果はこちら。
まだ数が少ないとはいえ、全部成功するのは気持ち良いものです。
Moq について
DI で挿入している依存クラスは、 Moq を使って自動でダミーデータを生成してもらうことができます。
ただ当然ながら Moq で生成したダミーデータのメソッドを呼んでも動作はしないため、その戻り値がテストに必要な場合、 Setup 、 Returns などを利用してダミーのデータを設定することができます。
制限事項として、 Setup で扱うには、そのメソッドが virtual である( override 可能)か、 class ではなく interface のモックを作る必要があります。
おわりに
俺たちの自動テストは始まったばかりだ。。。!
。。。というのは置いといて、 Moq を使って依存する class を自動生成できるのは結構便利ですね :)
まだどこをテストすべきか、といった部分が理解できていなかったり(なので作りながらテストを書いたり消したりしている)、 View や Model など他の機能のテストの書き方も分かっていなかったりするので、まぁ、まだこれからですがぼちぼち進めていきたいと思います。
参照
xUnit
- xUnit.net
- Unit Testing ASP.NET Core 2.1 Controllers – Henrik Olsson's Computer Software Notes
- .NET Core in Action
- ASP.NET Core のコントローラーのロジックをテストする - Microsoft Docs
- .NET Coreで単体テストを行う - Build Insider
- Unit Testing ASP.NET Core Applications - DotNetCurry
- ASP.NET Core で統合テスト - Microsoft Docs