使用Microsoft.AspNetCore.TestHost进行完整的功能测试
簡(jiǎn)介
Microsoft.AspNetCore.TestHost是可以用于Asp.net Core 的功能測(cè)試工具。很多時(shí)候我們一個(gè)接口寫好了,單元測(cè)試什么的也都o(jì)k了,需要完整調(diào)試一下,檢查下單元測(cè)試未覆蓋到的代碼是否有bug。步驟為如下:程序打個(gè)斷點(diǎn)->F5運(yùn)行->通常需要登錄個(gè)測(cè)試賬號(hào)->查找要調(diào)試api的入口->獲得斷點(diǎn)開(kāi)始調(diào)試=>代碼報(bào)錯(cuò)?很多時(shí)候需要停止調(diào)試修改->回到第一步。如此反復(fù)循環(huán),做著重復(fù)的工作,Microsoft.AspNetCore.TestHost正是為了解決這個(gè)問(wèn)題,它可以讓你使用xTest或者M(jìn)STest進(jìn)行覆蓋整個(gè)HTTP請(qǐng)求生命周期的功能測(cè)試。
進(jìn)行一個(gè)簡(jiǎn)單的功能測(cè)試
新建一個(gè)Asp.net Core WebApi和xUnit項(xiàng)目
ValuesController里面自帶一個(gè)Action
我們?cè)趚Unit項(xiàng)目里面模擬訪問(wèn)這個(gè)接口,首選安裝如下nuget包:
-
Microsoft.AspNetCore.TestHost
-
Microsoft.AspNetCore.All(很多依賴懶得找的話直接安裝這個(gè)集成包,百分之90涉及到AspNetCore的依賴都包含在里面)
然后需要引用被測(cè)試的AspnetCoreFunctionalTestDemo項(xiàng)目,新建一個(gè)測(cè)試類ValuesControllerTest
將GetValuesTest方法替換為如下代碼,其中startup類是應(yīng)用自AspnetCoreFunctionalTestDemo項(xiàng)目
此時(shí)在ValueController打下斷點(diǎn)
?
運(yùn)行GetValuesTest調(diào)試測(cè)試
成功進(jìn)入斷點(diǎn),我們不用啟動(dòng)瀏覽器,就可以進(jìn)行完整的接口功能測(cè)試了。
修改內(nèi)容目錄與自動(dòng)授權(quán)
上面演示了如何進(jìn)行一個(gè)簡(jiǎn)單的功能測(cè)試,但是存在兩個(gè)缺陷:
webApi在測(cè)試的時(shí)候?qū)嶋H的運(yùn)行目錄是在FunctionalTest目錄下
對(duì)需要授權(quán)的接口不能正常測(cè)試,會(huì)得到未授權(quán)的返回結(jié)果
?1.內(nèi)容目錄
我們可以在Controller的Get方法輸出當(dāng)前的內(nèi)容目錄
內(nèi)容目錄是在測(cè)試x項(xiàng)目下這與我們的預(yù)期不符,如果webapi項(xiàng)目對(duì)根目錄下的文件有依賴關(guān)系例如appsetting.json則會(huì)找不到該文件,解決的辦法是在webHost中手動(dòng)指定運(yùn)行根目錄
[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">如果項(xiàng)目與解決方案文件不在一個(gè)目錄,例如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的項(xiàng)目所在路徑,此時(shí)我們?cè)龠\(yùn)行
2.自動(dòng)授權(quán)
每次測(cè)試時(shí)手動(dòng)登錄這是一件很煩人的事情,所以我們希望可以自動(dòng)話,這里演示的時(shí)cookie方式的自動(dòng)授權(quán)
首先在startup文件配置cookie認(rèn)證
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認(rèn)證失敗的默認(rèn)操作改為返回401狀態(tài)碼。
在valuesController新增登錄的Action并配置Get的Action需要授權(quán)訪問(wèn)
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();}} }
此時(shí)我們使用測(cè)試項(xiàng)目測(cè)試Get方法
如我們預(yù)期,返回了401,說(shuō)明未授權(quán)。我們修改下GetValuesTest
?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">如果項(xiàng)目與解決方案文件不在一個(gè)目錄,例如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}.");}} }
我們首先訪問(wèn)api/Values/Login,獲取到Cookie,然后講cookie附在httpclient的默認(rèn)http頭上,這樣就能夠成功訪問(wèn)需要授權(quán)的接口了
總結(jié)
通過(guò)上面演示,我們已經(jīng)可以很大程度地模擬了整個(gè)api請(qǐng)求,讓我們可以方便地一鍵調(diào)試目標(biāo)接口,再也不用開(kāi)瀏覽器或postman了。
附上演示項(xiàng)目地址:https://github.com/huanent/AspnetCoreFunctionalTestDemo
原文:http://www.cnblogs.com/huanent/p/7886282.html
.NET社區(qū)新聞,深度好文,歡迎訪問(wèn)公眾號(hào)文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的使用Microsoft.AspNetCore.TestHost进行完整的功能测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 可观测性与原生云监控
- 下一篇: Entity Framework Cor