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

摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构【12-xUnit单元测试之集成测试】

使用xUnit借助TestServer进行集成测试,在单元测试中对WebApi的每个接口进行测试

文章目录

此分支项目代码

本章节介绍了使用xUnit借助TestServer进行集成测试,在单元测试中对WebApi的每个接口进行测试

新建单元测试

在tests解决方案文件夹下新建xUnit单元测试,记得存放在实际tests路径下,取名WebApiTests

添加包引用

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类:

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一个依赖注入容器,自己注册服务,然后在测试用例里自己解析,也一样可以做到测试组件的目的

ASP.NET Core搭建多层网站架构【12-xUnit单元测试之集成测试】的更多相关文章

  1. ASP.NET Core搭建多层网站架构【3-xUnit单元测试之简单方法测试】

    2020/01/28, ASP.NET Core 3.1, VS2019, xUnit 2.4.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[3-xUnit单元测试 ...

  2. ASP.NET Core搭建多层网站架构【2-公共基础库】

    2020/01/28, ASP.NET Core 3.1, VS2019,Newtonsoft.Json 12.0.3, Microsoft.AspNetCore.Cryptography.KeyDe ...

  3. ASP.NET Core搭建多层网站架构【0-前言】

    2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构 目录 0-前言 1-项目结构分层建立 2-公共基 ...

  4. ASP.NET Core搭建多层网站架构【1-项目结构分层建立】

    2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[1-项目结构分层建立] 文章目录 此分支项目代码 ...

  5. ASP.NET Core搭建多层网站架构【4-工作单元和仓储设计】

    2020/01/28, ASP.NET Core 3.1, VS2019, Microsoft.EntityFrameworkCore.Relational 3.1.1 摘要:基于ASP.NET Co ...

  6. ASP.NET Core搭建多层网站架构【5-网站数据库实体设计及映射配置】

    2020/01/29, ASP.NET Core 3.1, VS2019, EntityFrameworkCore 3.1.1, Microsoft.Extensions.Logging.Consol ...

  7. ASP.NET Core搭建多层网站架构【6-注册跨域、网站核心配置】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  8. ASP.NET Core搭建多层网站架构【7-使用NLog日志记录器】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  9. ASP.NET Core搭建多层网站架构【8.1-使用ViewModel注解验证】

    2020/01/29, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[8.1-使用ViewModel注解验证] 使用V ...

随机推荐

  1. leetcode全部滑动窗口题目总结C++写法(完结)

    3. 无重复字符的最长子串 A: 要找最长的无重复子串,所以用一个map保存出现过的字符,并且维持一个窗口,用le和ri指针标识.ri为当前要遍历的字符,如果ri字符在map中出现过,那么将le字符从 ...

  2. ubuntu 离线装包

    1,为什么要离线装包 防止有些包官方升级以后导致的不兼容,比如php5和php7,目前已经无法apt-get install php5了, 2,装包以前你得有安装包文件,deb或者是run deb包在 ...

  3. 解决修改JDK环境变量不生效方法

    解决修改JDK环境变量不生效方法 brupsuit1.7在安装时一直报错jdk版本低,我就将jdk1.6版本的卸了换成1.8的,结果修改了环境变量但它一直给我不生效.... 1.之前版本未卸载干净 进 ...

  4. 【StarUML】 活动图

    StarUML中的活动图本质上是流程图,活动图相对来说,更加专业,它有对信号的处理,对状态动作.数据区别表示,使得更清晰地了解控制流的走向. 1.基本元素 a.活动状态图(Activity).动作状态 ...

  5. Codeforces Round #530 (Div. 2) D. Sum in the tree 树上贪心

    D. Sum in the tree 题意 给出一颗树,奇数层数的点有值,值代表从1到该点的简单路的权值的和,偶数层数的点权值被擦去了 问所有节点的和的最小可能是多少 思路 对于每一个-1(也就是值未 ...

  6. 使用spring中遇到"java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor"问题

    项目中缺少aopalliance的jar包,下载一个相应的jar加入项目中就可以解决问题. 下载链接:http://www.java2s.com/Code/Jar/a/Downloadaopallia ...

  7. makecert 产出证书

    C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin>makecert -r -n // -e // -sv mymuse.pvk my ...

  8. 如何通过源码包的方式在linux安装python36

    背景: python34的安装非常简单,直接用yum就可以安装,但是安装最新版的python36通过yum方式是不行的,需要通过源码包进行安装 具体步骤如下: 1.安装openssl静态库[pip3安 ...

  9. 谈谈我对Promise的理解

    一.Promise是什么? Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大. ES6 将其写进了语言标准,统一了用法,原生提供了Pr ...

  10. 左偏树 (p3261) 对我来说是一道进阶题

    题意:有n座城池,m个人: 每座城池有一个耐久度: 每座城池有一个父亲城池(肯定会形成一棵树),还有flag base (这个看题意) 每个人有一个战力值和一个开始进攻的城池序号: 问:1.每个城池能 ...