­

使用Microsoft.AspNetCore.TestHost進行完整的功能測試

  • 2019 年 12 月 11 日
  • 筆記

來源:http://www.51testing.com/

簡介

  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(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://wap.ztestin.com/site/register?usercode=FAAAQwMQGAAXAwQBA3QhExcDHAQDPjVaABMIQg%3D%3D