天天看點

開發筆記:用Owin Host實作脫離IIS跑Web API單元測試

今天在開發一個ASP.NET Web API項目寫單元測試時,實在無法忍受之前的笨方法,決定改過自新。這次經曆再次證明了,當有一個問題影響你寫代碼的樂趣時,一定要盡早下定決心解決它,否則它浪費的時間很可能是解決這個問題所需時間的n倍,而且很多時候解決一個問題的難易程度取決于你下的決心有多大。

今天在開發一個ASP.NET Web API項目寫單元測試時,實在無法忍受之前的笨方法,決定改過自新。

之前Web API的單元測試需要進行以下的操作:

初始配置:

1)在IIS中建立一個站點指定Web API項目

2)在hosts加上該站點的IP位址解析

每次修改代碼:

3)修改代碼之後按F6編譯

4)用TestDriven.Net運作單元測試

一看就知道這個方法好土、好笨、好受罪。理想的方式應該是:無需任何初始配置,修改代碼之後無需按F6編譯,直接運作單元測試,一步完成操作。

今天在受不了舊方式的折磨、經不起理想方式的誘惑的情況下,下定決心要解決這個問題,最終通過Owin Host實作了,通過這篇博文分享一下。

用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,會在單元測試前自動進行編譯)

開發筆記:用Owin Host實作脫離IIS跑Web API單元測試

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中。

【參考資料】 

ASP.NET Web API Integration Testing with One Line of Code