ASP.NET Core搭建多層網站架構【12-xUnit單元測試之集成測試】

2020/02/01, ASP.NET Core 3.1, VS2019, xunit 2.4.1, Microsoft.AspNetCore.TestHost 3.1.1html

摘要:基於ASP.NET Core 3.1 WebApi搭建後端多層網站架構【12-xUnit單元測試之集成測試】
使用xUnit藉助TestServer進行集成測試,在單元測試中對WebApi的每一個接口進行測試git

文章目錄github

此分支項目代碼web

本章節介紹了使用xUnit藉助TestServer進行集成測試,在單元測試中對WebApi的每一個接口進行測試數據庫

新建單元測試

在tests解決方案文件夾下新建xUnit單元測試,記得存放在實際tests路徑下,取名WebApiTestsjson

添加包引用

WebApiTests單元測試添加Microsoft.AspNetCore.TestHost包引用:後端

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.1" />
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
  <PackageReference Include="xunit" Version="2.4.1" />
  <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
  <PackageReference Include="coverlet.collector" Version="1.2.0">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
</ItemGroup>

添加Microsoft.AspNetCore.TestHost包(3.1.1)引用,其餘默認的包升級到最新,具體版本參考上面
WebApiTests單元測試添加對MS.WebApi的引用服務器

接口測試

創建TestServerHost

WebApiTests單元測試中添加TestHostBuild.cs類,這是整個集成測試的核心部分:架構

using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using MS.Component.Jwt;
using MS.Component.Jwt.UserClaim;
using MS.WebApi;
using System.Net.Http;

namespace WebApiTests
{
    public static class TestHostBuild
    {
        public static readonly JwtService jwtService = new JwtService(Options.Create(new JwtSetting
        {
            Audience = "MS.Audience",
            Issuer = "MS.WebHost",
            LifeTime = 1440,
            SecurityKey = "MS.WebHost SecurityKey"//此處內容需和服務器appsettings.json中保持一致
        }));
        public static readonly UserData userData = new UserData
        {
            Account = "test",
            Email = "test@qq.com",
            Id = 1,
            Name = "測試用戶",
            Phone = "123456789111",
            RoleDisplayName = "testuserRole",
            RoleName = "testuser"
        };//測試用戶的數據,也能夠改爲真實的數據,看需求

        public static IHostBuilder GetTestHost()
        {
            //代碼和網站Program中CreateHostBuilder代碼很相似,去除了AddNlogService以避免跑測試生成不少日誌
            //若是網站並無使用autofac替換原生DI容器,UseServiceProviderFactory這句話能夠去除
            //關鍵是webBuilder中的UseTestServer,創建TestServer用於集成測試
            return new HostBuilder()
            .UseServiceProviderFactory(new AutofacServiceProviderFactory())//替換autofac做爲DI容器
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                .UseTestServer()//關鍵時多了這一行創建TestServer
                .UseStartup<Startup>();
            });
        }
        /// <summary>
        /// 生成帶token的httpclient
        /// </summary>
        /// <param name="host"></param>
        /// <returns></returns>
        public static HttpClient GetTestClientWithToken(this IHost host)
        {
            var client = host.GetTestClient();
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {GenerateToken()}");//把token加到Header中
            return client;
        }

        /// <summary>
        /// 生成jwt令牌
        /// </summary>
        /// <returns></returns>
        public static string GenerateToken()
        {
            return jwtService.BuildToken(jwtService.BuildClaims(userData));
        }
    }
}
  • new了一個JwtService,用於生成token,其中JwtSetting的設置要和服務器保持一致(也能夠直接從appsettings.json中讀取)
  • new了一個UserData,用於測試的僞造數據,若是有需求也能夠改爲數據庫中的真實數據
  • 其中GetTestHost方法中的代碼和網站Program中CreateHostBuilder代碼很相似
  • 去除了AddNlogService以避免跑測試生成不少日誌
  • 若是網站並無使用autofac替換原生DI容器,UseServiceProviderFactory這句話能夠去除
  • 關鍵是webBuilder中的UseTestServer,創建TestServer用於集成測試
  • 得到Host該怎麼寫,能夠看官方的測試用例,以上的代碼和以後的測試用例就是我參考官方的寫出來的
  • GetTestClientWithToken方法就是得到一個帶token的httpclient,基於GetTestClient方法而來的
  • 若有不須要帶token的請求,也能夠直接用GetTestClient方法

構建接口返回值對象

WebApiTests單元測試中添加ApiResult.cs類:app

namespace WebApiTests
{
    public class ApiResult<T>
    {
        public int status { get; set; }
        public T data { get; set; }
    }
}

因爲咱們的接口返回值統一包裝了一層,因此構建了ApiResult用於反序列化接口返回值對象

編寫接口的測試用例

WebApiTests單元測試中添加RoleControllerTest.cs類,這是Role接口的測試用例:

using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
using MS.Common.Extensions;
using MS.Entities;
using MS.Models.ViewModel;
using MS.WebCore.Core;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace WebApiTests
{
    public class RoleControllerTest
    {
        const string _testUrl = "/role/";
        const string _mediaType = "application/json";
        readonly Encoding _encoding = Encoding.UTF8;

        [Theory]
        [InlineData(1222538617050763264)]
        public async Task Delete_Id_ReturnResult(long id)
        {
            //arrange
            string url = $"{_testUrl}?id={id.ToString()}";// url:  /role/?id=11111111
            using var host = await TestHostBuild.GetTestHost().StartAsync();//啓動TestServer

            //act
            var response = await host.GetTestClientWithToken().DeleteAsync(url);//調用Delete接口
            var result = (await response.Content.ReadAsStringAsync()).GetDeserializeObject<ApiResult<ExecuteResult>>();//得到返回結果並反序列化

            //assert
            Assert.Equal(result.data.IsSucceed, string.IsNullOrWhiteSpace(result.data.Message));
        }
        [Fact]
        public async Task Post_CreateRole_ReturnTrue()
        {
            //arrange
            RoleViewModel viewModel = new RoleViewModel
            {
                Name = "RoleForPostTest",
                DisplayName = "RoleForPostTest"
            };
            StringContent content = new StringContent(viewModel.ToJsonString(), _encoding, _mediaType);//定義post傳遞的參數、編碼和類型
            using var host = await TestHostBuild.GetTestHost().StartAsync();//啓動TestServer

            //act
            var response = await host.GetTestClientWithToken().PostAsync(_testUrl, content); //調用Post接口
            var result = (await response.Content.ReadAsStringAsync()).GetDeserializeObject<ApiResult<ExecuteResult<Role>>>();//得到返回結果並反序列化

            //assert
            Assert.True(result.data.IsSucceed);

            //測完把添加的刪除
            await Delete_Id_ReturnResult(result.data.Result.Id);
        }

        [Fact]
        public async Task Put_UpdateRole_ReturnTrue()
        {
            //arrange
            RoleViewModel viewModel = new RoleViewModel
            {
                Name = "RoleForPutTest",
                DisplayName = "RoleForPutTest"
            };
            StringContent content = new StringContent(viewModel.ToJsonString(), _encoding, _mediaType);//定義put傳遞的參數、編碼和類型
            using var host = await TestHostBuild.GetTestHost().StartAsync();//啓動TestServer
            var response = await host.GetTestClientWithToken().PostAsync(_testUrl, content);//先添加一個用於更新測試 
            viewModel.Id = (await response.Content.ReadAsStringAsync()).GetDeserializeObject<ApiResult<ExecuteResult<Role>>>().data.Result.Id;
            content = new StringContent(viewModel.ToJsonString(), _encoding, _mediaType);

            //act
            response = await host.GetTestClientWithToken().PutAsync(_testUrl, content);
            var result = (await response.Content.ReadAsStringAsync()).GetDeserializeObject<ApiResult<ExecuteResult>>();

            //assert
            Assert.True(result.data.IsSucceed);

            //測完把添加的刪除
            await Delete_Id_ReturnResult(viewModel.Id);
        }
    }
}
  • 用於測試的參數準備好後,啓動TestServer
  • 從TestServer中獲取咱們自定義帶Token的HttpClient用於接口測試請求
  • 有個名爲Delete_Id_ReturnResult的測試,使用了參數,因此改成Theory特性而不是Fact,繼而給出了InlineData用做默認參數測試

打開測試管理器,運行測試,測試都經過:

項目完成後,以下圖所示

說明

  • 以上即是模擬服務端和客戶端通訊,從而集成測試整個網站的接口
  • 若是不想對整個網站集成測試,而只是測試某個服務、組件,能夠考慮使用moq
  • 說一個不太正規的偏門辦法,能夠在單元測試中本身new一個依賴注入容器,本身註冊服務,而後在測試用例裏本身解析,也同樣能夠作到測試組件的目的
相關文章
相關標籤/搜索