【ASP.NET Core】【xUnit】【Moq】ユニットテストを追加する 1
はじめに
今回は既存の ASP.NET Core プロジェクトにテストを追加してみます。 ASP.NET Core のテストは以前もやりましたが、今回も xUnit と Moq を使います。
Environments
- .NET Core ver.3.1.402
- xUnit ver.2.4.0
- Moq ver.4.14.5
Base project
元のプロジェクトは「dotnet new empty -n XUnitSample」で作ったものにいくつかクラスを加えています。
Startup.cs
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Services; namespace XUnitSample { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddControllers(); services.AddScoped<IProductService, ProductService>(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseStaticFiles(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }
Product.cs
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Models { [Table("Product")] public class Product { [Key] public int Id { get; set; } public string? ModelName { get; set; } } }
HomeController.cs
using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Models; using Services; namespace Controllers { public class HomeController: Controller { private readonly IProductService _product; public HomeController(IProductService product) { _product = product; } [Route("/")] public IActionResult Index() { ViewData["Title"] = "Home"; return View("Views/Index.cshtml"); } [Route("/Products/All")] public async Task<List<Product>> GetAllProducts() { return await _product.GetProductsAsync(); } } }
IProductService.cs
using System.Collections.Generic; using System.Threading.Tasks; using Models; namespace Services { public interface IProductService { Task<List<Product>> GetProductsAsync(); } }
ProductService.cs
using System.Collections.Generic; using System.Threading.Tasks; using Models; namespace Services { public class ProductService: IProductService { public async Task<List<Product>> GetProductsAsync() { return await Task.FromResult(new List<Product>{ new Product { Id = 0, ModelName = "SampleModel" } }); } } }
_Layout.cshtml
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <title>@ViewData["Title"]</title> </head> <body> @RenderBody() </body> </html>
Index.cshtml
<div>Hello World</div>
テストプロジェクトを追加する
まずはテスト用のプロジェクトを追加するわけですが、既存のプロジェクト以下に作ってしまうとエラーが発生します。
obj\Debug\netcoreapp3.1\.NETCoreApp,Version=v3.1.AssemblyAttributes.cs(4,12): error CS0579: Duplicate 'global::System.Runtime.Versioning.TargetFrameworkAttribute' attribute [C:\Users\example\OneDrive\Documents\workspace\Dotnet31\AspNetCore31Sample\AspNetCore31Sample.csproj] obj\Debug\netcoreapp3.1\AspNetCore31Sample.AssemblyInfo.cs(13,12): error CS0579: Duplicate 'System.Reflection.AssemblyCompanyAttribute' attribute ...
ということで、まずソリューション( .sln )を作成し、そこにプロジェクトを追加する必要があります。
dotnet new sln -n XUnit dotnet sln add XUnitSample
Add xUnit project
さて、それでは「xunit」テンプレートでテスト用のプロジェクトを追加します。
dotnet new xunit -n XUnitSampleTest dotnet sln add XUnitSampleTest dotnet add reference ../XUnitSample/XUnitSample.csproj
Moq も追加します。
XUnitSampleTest.csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0"/> <PackageReference Include="xunit" Version="2.4.0"/> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0"/> <PackageReference Include="coverlet.collector" Version="1.2.0"/> <PackageReference Include="Moq" Version="4.14.5"/> </ItemGroup> <ItemGroup> <ProjectReference Include="..\XUnitSample\XUnitSample.csproj"/> </ItemGroup> </Project>
テストクラスを作る
「HomeController」をテストするクラスを追加してみます。
HomeControllerTest.cs
using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Models; using Moq; using Services; using Xunit; namespace Controllers { public class HomeControllerTest { private readonly HomeController _target; public HomeControllerTest() { var productMock = new Mock<IProductService>(); productMock.Setup(p => p.GetProductsAsync()) .ReturnsAsync(new List<Product> { new Product { Id = 1, ModelName = "ModelTest" } }); _target = new HomeController(productMock.Object); } [Fact] public async Task GetAllProductsReturnsOneItem() { Assert.True((await _target.GetAllProducts()).Count == 1); } [Fact] public void IndexReturnsView() { Assert.IsType<ViewResult>(_target.Index()); } [Fact] public void PageTitleIsHome() { var page = _target.Index(); var viewResult = Assert.IsType<ViewResult>(page); Assert.Equal("Home", viewResult.ViewData["Title"].ToString()); } } }
「dotnet test」コマンドでテスト結果が得られます。
Visual Studio Code でテストを実行する
「dotnet test」コマンドでも問題はないのですが、やっぱり下記のような見た目が欲しいところ。
ということで、 VSCode で拡張機能を追加してみます。
( Visual Studio でやればもっと簡単なのでしょうが、 VSCode が好きなので。。。)
「.NET Core Test Explorer」という名前の拡張機能は二つあるのですが、テスト前にビルドやテストの更新などをしなくてよい、という点でこちらの方が好きですね。
Resources
- Home > xUnit.net
- Getting started: .NET Core with command line > xUnit.net
- Moq : Mocking Framework for .NET - Qiita
- GitHub - moq/moq4: Repo for managing Moq 4.x
- dotnet テストと xUnit を使用した .NET Core での単体テスト C# コード - .NET Core | Microsoft Docs
- ASP.NET Core のコントローラーのロジックをテストする | Microsoft Docs
- Moq : Mocking Framework for .NET - Qiita
- c# - What's the idiomatic way to verify collection size in xUnit? - Stack Overflow
- xUnit.net でユニットテストを始める - Qiita