使用Microsoft.AspNetCore.TestHost进行完整的功能测试

来源:互联网 发布:淘宝童装店铺名 编辑:程序博客网 时间:2024/06/05 11:41

简介

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认证

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(0, 0, 30);                   o.Events.OnRedirectToLogin = (context) =>                   {                       context.Response.StatusCode = 401;                       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需要授权访问

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


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

原文:http://www.cnblogs.com/huanent/p/7886282.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

原创粉丝点击