简介

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调试测试

成功进入断点,我们不用启动浏览器,就可以进行完整的接口功能测试了。

修改内容目录与自动授权

上面演示了如何进行一个简单的功能测试,但是存在两个缺陷:

  1. webApi在测试的时候实际的运行目录是在FunctionalTest目录下
  2. 对需要授权的接口不能正常测试,会得到未授权的返回结果

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

最新文章

  1. opendaylight的Beryllium安装
  2. a链接的四种状态
  3. 常用的Webservice接口
  4. SSIS 包单元测试检查列表
  5. windows版爬取csdn
  6. WCF的回调使用实例代码说明
  7. js数组的操作及数组与字符串的相互转化
  8. Python监控网站运行状况
  9. RabbitMQ-从基础到实战(6)— 与Spring集成
  10. svn 批量加入没有加入版本号控制的文件命令
  11. 神奇的Python
  12. Java必须了解的“递归”与“IO流”!!!
  13. [转] Java程序员学C#基本语法两个小时搞定(对比学习)
  14. Reids 持久化AOF 重写实现原理
  15. 酷学习笔记——ASP.NET Core 简介
  16. Android Bug分析系列:第三方平台安装app启动后,home键回到桌面后点击app启动时会再次启动入口类bug的原因剖析
  17. python全栈开发day29-网络编程之socket常见方法,socketserver模块,ftp作业
  18. 配置ssh免密登录
  19. Pick-up sticks
  20. Unity注意事项

热门文章

  1. 系统讲解CSS,前端开发最神奇的技术,新手的你一定不能错过
  2. Kotlin——最详细的控制语句使用
  3. oracle 表空间不足解决办法
  4. 雅虎军规以及Chrome调试
  5. LeetCode 110. Balanced Binary Tree (平衡二叉树)
  6. LeetCode 64. Minimum Path Sum(最小和的路径)
  7. Myeclipse常见快捷键及配置
  8. linux云服务器常用设置
  9. 使用angular4和asp.net core 2 web api做个练习项目(三)
  10. Luck and Love(二维线段树)