WebAPI的一種單元測試方案
大家是如何對webApi寫測試的呢?
1.利用Fiddler直接做請求,觀察response的內容。
2.利用Httpclient做請求,斷言response的內容。
3.直接呼叫webApi的action,這種方式的測試跟真實的呼叫還是有一定差距,不夠完美。
一、首先寫一個OrderController用來做測試用
C#12345678910111213141516171819202122232425 | publicclassOrderController:ApiController{// GET api/orderpublicOrder Get(){returnnewOrder(){Id=1,Descriptions="descriptions",Name="name"};}// GET api/order/5publicstringGet(intid){return"value";}// POST api/orderpublicOrder Post |
二、WebApi的請求過程
webApi的核心是對訊息的管道處理,整個核心是有一系列訊息處理器(HttpMessageHandler)首尾連線的雙向管道,管道頭為HttpServer,管道尾為HttpControllerDispatcher,HttpControllerDispatcher負責對controller的啟用和action的執行,然後相應的訊息逆向流出管道。
所以我們可以利用HttpMessageInvoker將一個請求訊息HttpRequestMessage傳送到管道中,最後收到的訊息HttpResponseMessage就代表一個真實的請求響應。
三、Get請求的測試
C#123456789101112131415161718192021 | [Test]publicvoidGetTest(){stringbaseAddress="http://localhost:33203/";HttpConfiguration config=newHttpConfiguration();WebApiConfig.Register(config);config.IncludeErrorDetailPolicy=IncludeErrorDetailPolicy.Always;HttpServer server=newHttpServer(config);HttpMessageInvoker messageInvoker=newHttpMessageInvoker(server);CancellationTokenSource cts=newCancellationTokenSource();HttpRequestMessage request=newHttpRequestMessage(HttpMethod.Get,baseAddress+"api/order");using(HttpResponseMessage response=messageInvoker.SendAsync(request,cts.Token).Result){varcontent=response.Content.ReadAsStringAsync().Result;varresult=JsonConvert.DeserializeObject(content);result.Name.Should().Be("name");}} |
四、Post請求的測試
C#1234567891011121314151617181920 | [Test]publicvoidPostTest(){stringbaseAddress="http://localhost:33203/";HttpConfiguration config=newHttpConfiguration();WebApiConfig.Register(config);config.IncludeErrorDetailPolicy=IncludeErrorDetailPolicy.Always;HttpServer server=newHttpServer(config);HttpMessageInvoker messageInvoker=newHttpMessageInvoker(server);CancellationTokenSource cts=newCancellationTokenSource();HttpRequestMessage request=newHttpRequestMessage(HttpMethod.Post,baseAddress+"api/order");varorder=newOrder(){Id=1,Name="orderName",Descriptions="orderDescriptions"};request.Content=newObjectContent(order,newJsonMediaTypeFormatter());using(HttpResponseMessage response=messageInvoker.SendAsync(request,cts.Token).Result){varcontent=JsonConvert.SerializeObject(order,newJsonSerializerSettings(){ContractResolver=newCamelCasePropertyNamesContractResolver()});response.Content.ReadAsStringAsync().Result.Should().Be(content);}} |
四、重構
可以看到這兩個測試大部分的程式碼是相同的,都是用來發送請求。因此我們提取一個webApiTestBase類,該基類可以提供InvokeGetRequest,InvokePostRequest,InvokePutRequest等方法
C#1234567891011121314151617181920212223242526272829303132333435363738394041424344 | publicabstractclassApiTestBase{publicabstractstringGetBaseAddress();protectedTResult InvokeGetRequest(stringapi){using(varinvoker=CreateMessageInvoker()){using(varcts=newCancellationTokenSource()){varrequest=newHttpRequestMessage(HttpMethod.Get,GetBaseAddress()+api);using(HttpResponseMessage response=invoker.SendAsync(request,cts.Token).Result){varresult=response.Content.ReadAsStringAsync().Result;returnJsonConvert.DeserializeObject(result);}}}}protectedTResult InvokePostRequest(stringapi,TArguemnt arg){varinvoker=CreateMessageInvoker();using(varcts=newCancellationTokenSource()){varrequest=newHttpRequestMessage(HttpMethod.Post,GetBaseAddress()+api);request.Content=newObjectContent(arg,newJsonMediaTypeFormatter());using(HttpResponseMessage response=invoker.SendAsync(request,cts.Token).Result){varresult=response.Content.ReadAsStringAsync().Result;returnJsonConvert.DeserializeObject(result);}}}privateHttpMessageInvoker CreateMessageInvoker(){varconfig=newHttpConfiguration();WebApiConfig.Register(config);varserver=newHttpServer(config);varmessageInvoker=newHttpMessageInvoker(server);returnmessageInvoker;}} |
有了這個基類,我們寫測試只需要重寫方法GetBaseAddress(),然後直接呼叫基類方法並進行斷言即可
C#123456789101112131415161718192021222324252627282930 | [TestFixture]publicclassOrderApiTests:ApiTestBase{publicoverridestringGetBaseAddress(){return"http://localhost:33203/";}[Test]publicvoidShould_get_order_successfully(){varresult=InvokeGetRequest("api/order");result.Name.Should().Be("name");result.Descriptions.Should().Be("descriptions");result.Id.Should().Be(1);}[Test]publicvoidShould_post_order_successfully(){varnewOrder=newOrder(){Name="newOrder",Id=100,Descriptions="new-order-description"};varresult=InvokePostRequest("api/order",newOrder);result.Name.Should().Be("newOrder");result.Id.Should().Be(100);result.Descriptions.Should().Be("new-order-description");}} |
是不是乾淨多了。
這種in-memory的測試方案有什麼優點和缺點呢?
優點:
1.模擬真實呼叫,需要傳入api地址即可得到結果,由於整個呼叫是in-memory的,所有效率很高,很適合整合測試。
2.整個測試時可以除錯的,可以直接從單元測試除錯進去,如果你寫一個httpClient的測試,需要把webApi啟動起來,然後。。。麻煩
缺點:我覺得原文作者說的那些缺點都可以忽略不計。