使用Microsoft.AspNetCore.TestHost进行完整的功能测试
简介
Microsoft.AspNetCore.TestHost是可以用于Asp.net Core 的功能测试工具。很多时候我们一个接口写好了,单元测试什么的也都ok了,需要完整调试一下,检查下单元测试未覆盖到的代码是否有bug。步骤为如下:程序打个断点->F5运行->通常需要登录个测试账号->查找要调试api的入口->获得断点开始调试=>代码报错?很多时候需要停止调试修改->回到第一步。如此反复循环,做着重复的工作,Microsoft.AspNetCore.TestHost正是为了解决这个问题,它可以让你使用xTest或者MSTest进行覆盖整个HTTP请求生命周期的功能测试。
进行一个简单的功能测试
新建一个Asp.net Core WebApi和xUnit项目
ValuesController里面自带一个Action
我们在xUnit项目里面模拟访问这个接口,首选安装如下nuget包:
- Microsoft.AspNetCore.TestHost
- Microsoft.AspNetCore.All(很多依赖懒得找的话直接安装这个集成包,百分之90涉及到AspNetCore的依赖都包含在里面)
然后需要引用被测试的AspnetCoreFunctionalTestDemo项目,新建一个测试类ValuesControllerTest
将GetValuesTest方法替换为如下代码,其中startup类是应用自AspnetCoreFunctionalTestDemo项目
[Fact]
public void GetValuesTest()
{ var client = new TestServer(WebHost
.CreateDefaultBuilder()
.UseStartup<Startup>())
.CreateClient(); string result = client.GetStringAsync("api/values").Result; Assert.Equal(result, JsonConvert.SerializeObject(new string[] { "value1", "value2" }));
}
此时在ValueController打下断点
运行GetValuesTest调试测试
成功进入断点,我们不用启动浏览器,就可以进行完整的接口功能测试了。
修改内容目录与自动授权
上面演示了如何进行一个简单的功能测试,但是存在两个缺陷:
- webApi在测试的时候实际的运行目录是在FunctionalTest目录下
- 对需要授权的接口不能正常测试,会得到未授权的返回结果
1.内容目录
我们可以在Controller的Get方法输出当前的内容目录
内容目录是在测试x项目下这与我们的预期不符,如果webapi项目对根目录下的文件有依赖关系例如appsetting.json则会找不到该文件,解决的办法是在webHost中手动指定运行根目录
[Fact]
public void GetValuesTest()
{ var client = new TestServer(WebHost
.CreateDefaultBuilder()
.UseContentRoot(GetProjectPath("AspnetCoreFunctionalTestDemo.sln", "", typeof(Startup).Assembly))
.UseStartup<Startup>())
.CreateClient(); string result = client.GetStringAsync("api/values").Result; Assert.Equal(result, JsonConvert.SerializeObject(new string[] { "value1", "value2" }));
} /// <summary>
/// 获取工程路径
/// </summary>
/// <param name="slnName">解决方案文件名,例test.sln</param>
/// <param name="solutionRelativePath">如果项目与解决方案文件不在一个目录,例如src文件夹中,则传src</param>
/// <param name="startupAssembly">程序集</param>
/// <returns></returns>
private static string GetProjectPath(string slnName, string solutionRelativePath, Assembly startupAssembly)
{
string projectName = startupAssembly.GetName().Name;
string applicationBasePath = PlatformServices.Default.Application.ApplicationBasePath;
var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, slnName));
if (solutionFileInfo.Exists)
{
return Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath, projectName));
} directoryInfo = directoryInfo.Parent;
}
while (directoryInfo.Parent != null); throw new Exception($"Solution root could not be located using application root {applicationBasePath}.");
}
GetProjectPath方法采用递归的方式找到startup的项目所在路径,此时我们再运行
2.自动授权
每次测试时手动登录这是一件很烦人的事情,所以我们希望可以自动话,这里演示的时cookie方式的自动授权
首先在startup文件配置cookie认证
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.Cookies; namespace AspnetCoreFunctionalTestDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(o => o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.ExpireTimeSpan = new TimeSpan(, , );
o.Events.OnRedirectToLogin = (context) =>
{
context.Response.StatusCode = ;
return Task.CompletedTask;
};
});
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
}
}
这里覆盖了cookie认证失败的默认操作改为返回401状态码。
在valuesController新增登录的Action并配置Get的Action需要授权访问
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Security.Claims; namespace AspnetCoreFunctionalTestDemo.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet,Authorize]
public IEnumerable<string> Get([FromServices]IHostingEnvironment env)
{
return new string[] { "value1", "value2" };
} // POST api/values
[HttpGet("Login")]
public void Login()
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, "huanent"));
var principal = new ClaimsPrincipal(identity);
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal).Wait();
}
}
}
此时我们使用测试项目测试Get方法
如我们预期,返回了401,说明未授权。我们修改下GetValuesTest
using AspnetCoreFunctionalTestDemo;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.PlatformAbstractions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using static Microsoft.AspNetCore.WebSockets.Internal.Constants; namespace FunctionalTest
{
public class ValuesControllerTest
{ [Fact]
public void GetValuesTest()
{
var client = new TestServer(
WebHost.CreateDefaultBuilder()
.UseStartup<Startup>()
.UseContentRoot(GetProjectPath("AspnetCoreFunctionalTestDemo.sln", "", typeof(Startup).Assembly))
).CreateClient();
var respone = client.GetAsync("api/values/login").Result;
SetCookie(client, respone);
var result = client.GetAsync("api/values").Result;
} private static void SetCookie(HttpClient client, HttpResponseMessage respone)
{
string cookieString = respone.Headers.GetValues("Set-Cookie").First();
string cookieBody = cookieString.Split(';').First();
client.DefaultRequestHeaders.Add("Cookie", cookieBody);
} /// <summary>
/// 获取工程路径
/// </summary>
/// <param name="slnName">解决方案文件名,例test.sln</param>
/// <param name="solutionRelativePath">如果项目与解决方案文件不在一个目录,例如src文件夹中,则传src</param>
/// <param name="startupAssembly">程序集</param>
/// <returns></returns>
private static string GetProjectPath(string slnName, string solutionRelativePath, Assembly startupAssembly)
{
string projectName = startupAssembly.GetName().Name;
string applicationBasePath = PlatformServices.Default.Application.ApplicationBasePath;
var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, slnName));
if (solutionFileInfo.Exists)
{
return Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath, projectName));
} directoryInfo = directoryInfo.Parent;
}
while (directoryInfo.Parent != null); throw new Exception($"Solution root could not be located using application root {applicationBasePath}.");
}
}
}
我们首先访问api/Values/Login,获取到Cookie,然后讲cookie附在httpclient的默认http头上,这样就能够成功访问需要授权的接口了
总结
通过上面演示,我们已经可以很大程度地模拟了整个api请求,让我们可以方便地一键调试目标接口,再也不用开浏览器或postman了。
附上演示项目地址:https://github.com/huanent/AspnetCoreFunctionalTestDemo
使用Microsoft.AspNetCore.TestHost进行完整的功能测试的更多相关文章
- ASP.NET Core 源码阅读笔记(5) ---Microsoft.AspNetCore.Routing路由
这篇随笔讲讲路由功能,主要内容在项目Microsoft.AspNetCore.Routing中,可以在GitHub上找到,Routing项目地址. 路由功能是大家都很熟悉的功能,使用起来也十分简单,从 ...
- ASP.NET Core 源码阅读笔记(3) ---Microsoft.AspNetCore.Hosting
有关Hosting的基础知识 Hosting是一个非常重要,但又很难翻译成中文的概念.翻译成:寄宿,大概能勉强地传达它的意思.我们知道,有一些病毒离开了活体之后就会死亡,我们把那些活体称为病毒的宿主. ...
- Microsoft.AspNetCore.Routing路由
Microsoft.AspNetCore.Routing路由 这篇随笔讲讲路由功能,主要内容在项目Microsoft.AspNetCore.Routing中,可以在GitHub上找到,Routing项 ...
- ABP 找不到版本为 (>= 1.0.0-preview1-27891) 的包 Microsoft.AspNetCore.SignalR 错误
错误描述: 下载ABP模板项目3.4.1的版本(当前最新版本),编译加载nuget包Microsoft.AspNetCore.SignalR时会报如下错误: 严重性 代码 说明 ...
- Asp.NetCore+Microsoft.AspNetCore.SignalR前后端分离
1.新建WebApi 2.安装Microsoft.AspNetCore.SignalR 3.新建一个集线器和消息类 using Microsoft.AspNetCore.SignalR; using ...
- InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]'
在新建asp.net core 应用后, 添加了自定义的ApplicationDbContext 和ApplicationUser ,并添加了Identity认证后, 会出现 InvalidOpera ...
- ASP.NET Core 中 HttpContext 详解与使用 | Microsoft.AspNetCore.Http 详解 (转载)
“传导体” HttpContext 要理解 HttpContext 是干嘛的,首先,看图 图一 内网访问程序 图二 反向代理访问程序 ASP.NET Core 程序中,Kestrel 是一个基于 li ...
- 通过Microsoft.AspNetCore.App元包简化程序集的引用
Asp.net core下提供默认提供了一些在.net core不能直接使用的库,如日志.依赖注入.选项.通用主机.EntityFramework等,虽然我们可以通过Nuget的方式手动加载这些包,但 ...
- Orchard Core 版本冲突 The type 'FormTagHelper' exists in both 'Microsoft.AspNetCore.Mvc.TagHelpers, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' and...
最近老大让我看Orchard Core,这是一个CMS系统.可以先参考大佬的文章:https://www.cnblogs.com/shanyou/archive/2018/09/25/9700422. ...
随机推荐
- Nhibernate/Hibernate 使用多参数存儲過程 出現could not execute query,Could not locate named parameter等錯誤解決
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns=" ...
- [bzoj2131]免费的馅饼 树状数组优化dp
2131: 免费的馅饼 Time Limit: 10 Sec Memory Limit: 259 MB[Submit][Status][Discuss] Description Input 第一行是 ...
- [NOIP2014][DP]飞扬的小鸟
[NOIP2014]飞扬的小鸟 ——!x^n+y^n=z^n 题目描述: Flappy Bird 是一款风靡一时的休闲手机游戏.玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画 ...
- Chrome等浏览器下出现net::ERR_BLOCKED_BY_CLIENT的解决办法
当我们在做开发时,调试页面图片会出现部分图片无法正常显示,并且确认图片的地址正确: 按F12 Debug查看报错原因,提示net::ERR_BLOCKED_BY_CLIENT错误,但当我们点击图片地址 ...
- LeetCode Questions List (LeetCode 问题列表)- Java Solutions
因为在开始写这个博客之前,已经刷了100题了,所以现在还是有很多题目没有加进来,为了方便查找哪些没加进来,先列一个表可以比较清楚的查看,也方便给大家查找.如果有哪些题目的链接有错误,请大家留言和谅解, ...
- Akka(34): Http:Unmarshalling,from Json
Unmarshalling是Akka-http内把网上可传输格式的数据转变成程序高级结构话数据的过程,比如把Json数据转换成某个自定义类型的实例.按具体流程来说就是先把Json转换成可传输格式数据如 ...
- Android插件化-RePlugin项目集成与使用
前言:前一段时间新开源了一种全面插件化的方案-- RePlugin,之前一种都在关注 DroidPlugin 并且很早也在项目中试用了,但最终没有投入到真正的生产环节,一方面是项目中没有特别需要插件化 ...
- YYHS-猜数字(并查集/线段树维护)
题目描述 LYK在玩猜数字游戏. 总共有n个互不相同的正整数,LYK每次猜一段区间的最小值.形如[li,ri]这段区间的数字的最小值一定等于xi. 我们总能构造出一种方案使得LY ...
- 版本控制之四:SVN客户端重新设置帐号和密码(转)
在第一次使用TortoiseSVN从服务器CheckOut的时候,会要求输入用户名和密码,这时输入框下面有个选项是保存认证信息,如果选了这个选项,那么以后就不用每次都输入一遍用户名密码了. 不过,如果 ...
- windows 系统下C++实现的多线程
摘抄http://blog.csdn.net/huyiyang2010/article/details/5809919 Thread.h #ifndef __THREAD_H__ #define __ ...