zoukankan      html  css  js  c++  java
  • 使用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调试测试

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

    修改内容目录与自动授权

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

    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(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需要授权访问

    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

  • 相关阅读:
    sqlserver 日期格式化
    CentOS7系统 ansible自动化部署多台服务器部署
    Linux运维跳槽40道面试精华题
    Linux下SVN创建新的项目
    日志切割
    SVN的安装和启动SVN的安装
    jenkins+Gitlab+maven+tomcat实现自动集成、打包、部署
    nginx启动脚本
    nginx如何调用php
    redis常用命令
  • 原文地址:https://www.cnblogs.com/huanent/p/7886282.html
Copyright © 2011-2022 走看看