vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【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>

テストプロジェクトを追加する

まずはテスト用のプロジェクトを追加するわけですが、既存のプロジェクト以下に作ってしまうとエラーが発生します。

f:id:mslGt:20200913095625p:plain

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

f:id:mslGt:20200913095703p:plain

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」コマンドでテスト結果が得られます。

f:id:mslGt:20200913095737p:plain

Visual Studio Code でテストを実行する

dotnet test」コマンドでも問題はないのですが、やっぱり下記のような見た目が欲しいところ。

f:id:mslGt:20200913095803p:plain

ということで、 VSCode拡張機能を追加してみます。
( Visual Studio でやればもっと簡単なのでしょうが、 VSCode が好きなので。。。)

「.NET Core Test Explorer」という名前の拡張機能は二つあるのですが、テスト前にビルドやテストの更新などをしなくてよい、という点でこちらの方が好きですね。

Resources