今天在開發一個ASP.NET Web API項目寫單元測試時,實在沒法忍受以前的笨方法,決定改過自新。web
以前Web API的單元測試須要進行如下的操做:數據庫
初始配置:api
1)在IIS中建立一個站點指定Web API項目app
2)在hosts加上該站點的IP地址解析async
每次修改代碼:函數
3)修改代碼以後按F6編譯post
4)用TestDriven.Net運行單元測試單元測試
一看就知道這個方法好土、好笨、好受罪。理想的方式應該是:無需任何初始配置,修改代碼以後無需按F6編譯,直接運行單元測試,一步完成操做。測試
今天在受不了舊方式的折磨、經不起理想方式的誘惑的狀況下,下定決心要解決這個問題,最終經過Owin Host實現了,經過這篇博文分享一下。ui
用Owin Host實現的思路很簡單,就是在單元測試中以Owin Host運行ASP.NET Web API站點,而後單元測試代碼直接請求這個Owin Host站點進行測試。
咱們的Web API項目是基於ASP.NET 4.5 + ASP.NET Web API 5.2.3開發的,沒有OWIN相關的代碼,因此先要在Web API項目中添加一些代碼 ,以讓Owin Host可以加載之。
首先nuget安裝Owin包包(IAppBuilder在此包中):
PM> Install-Package Owin
而後添加Startup.cs:
public class Startup { public void Configuartion(IAppBuilder app) { } }
接着nuget安裝Microsoft.AspNet.WebApi.Owin包包(app.UseWebApi擴展方法在此包中)
PM> Install-Package Microsoft.AspNet.WebApi.Owin
在Startup.Configuratrion方法中添加代碼,調用WebApiConfig.Register方法(這個是以前已經實現的,路由配置就在其中)配置HttpConfiguration,而後將之註冊到OWIN的管線中。
public class Startup { public void Configuration(IAppBuilder app) { var configuraton = new HttpConfiguration(); WebApiConfig.Register(configuraton); app.UseWebApi(configuraton); } }
Web API項目只需這樣簡單改造一下,就能夠支持Owin Host,無任何反作用,不影響用IIS部署站點。
單元測試代碼的改造也很簡單,只需在跑測試以前用Microsoft.Owin.Hosting中的WebApp.Start()方法加載Web API站點。
首先nuget安裝Owin Host的包包:
PM> Install-Package Microsoft.Owin.Hosting PM> Install-Package Microsoft.Owin.Host.HttpListener
接着在測試類的構造函數中用WebApp.Start()啓動Web API站點:
public class CommentsWebApiTest : IDisposable { private const string HOST_ADDRESS = "http://localhost:8001"; private IDisposable _webApp; public CommentsWebApiTest() { _webApp = WebApp.Start<Startup>(HOST_ADDRESS); Console.WriteLine("Web API started!"); } public void Dispose() { _webApp.Dispose(); } }
而後就能夠脫離IIS無比輕鬆地進行Web API的單元測試了。
下面來實際體驗一下:
1)在Web API項目中實現一個ApiController
public class CommentsController : ApiController { [Route("blogposts/{postId}/comments")] public async Task<IHttpActionResult> Get(int postId) { var comments = new Comment[] { new Comment { PostId = postId, Body = "Coding changes the world1" } }; return Ok<Comment[]>(comments); } }
2)編寫基於Owin Host跑Web API站點的單元測試代碼
public class CommentsWebApiTest : IDisposable { private const string HOST_ADDRESS = "http://localhost:8001"; private IDisposable _webApp; private HttpClient _httClient; public CommentsWebApiTest() { _webApp = WebApp.Start<Startup>(HOST_ADDRESS); Console.WriteLine("Web API started!"); _httClient = new HttpClient(); _httClient.BaseAddress = new Uri(HOST_ADDRESS); Console.WriteLine("HttpClient started!"); } public void Dispose() { _httClient.Dispose(); _webApp.Dispose(); } [Fact] public async Task GetComments() { var postId = 1; var response = await _httClient.GetAsync($"/blogposts/{postId}/comments"); if(response.StatusCode != HttpStatusCode.OK) { Console.WriteLine(await response.Content.ReadAsStringAsync()); } Assert.Equal(HttpStatusCode.OK, response.StatusCode); var comments = await response.Content.ReadAsAsync<Comment[]>(); Assert.NotEmpty(comments); Assert.Equal(postId, comments[0].PostId); Assert.Equal("Coding changes the world", comments[0].Body); } }
注:除了nuget安裝Microsoft.Owin.Hosting與Microsoft.Owin.Host.HttpListener包包,還要安裝Microsoft.AspNet.WebApi.Client包包(ReadAsAsync<Comment[]>在此包中)。
3)運行單元測試:在單元測試方法中點擊鼠標右鍵並點擊Run Test(s)(用的是TestDriven.Net,會在單元測試前自動進行編譯)
4)查看單元測試結果,驗證測試Web API的理想方式是否實現:
Output from WebApiTests.CommentsWebApiTest.GetComments: Web API started! HttpClient started! 1 passed, 0 failed, 0 skipped, took 4.91 seconds (xUnit.net 1.9.2 build 1705).
測試經過!理想方式實現!
此次經歷再次證實了,當有一個問題影響你寫代碼的樂趣時,必定要儘早下定決心解決它,不然它浪費的時間極可能是解決這個問題所需時間的n倍,並且不少時候解決一個問題的難易程度取決於你下的決心有多大。
【更新】
須要注意一個地方,在單元測試中以owin host運行web api站點時,配置信息(好比數據庫鏈接字符串)是從單元測試項目的app.config中讀取,而不是從web api項目的web.config中讀取,因此要將web.config中的相關配置複製到app.config中。
【參考資料】