大家是如何对webApi写测试的呢?
1.利用Fiddler直接做请求,观察response的内容。
2.利用Httpclient做请求,断言response的内容。
3.直接调用webApi的action,这种方式的测试跟真实的调用还是有一定差距,不够完美。
接下来我介绍一种webApi的in-memory调用方法,也能够达到对webApi的测试,并且由于是in-memory调用,效率也比较高,非常适写单元测试。本文参考了In memory client, host and integration testing of your Web API service。
一、首先写一个OrderController用来做测试用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
class
OrderController : ApiController
{
// GET api/order
public
Order Get()
{
return
new
Order(){Id = 1,Descriptions =
"descriptions"
,Name =
"name"
};
}
// GET api/order/5
public
string
Get(
int
id)
{
return
"value"
;
}
// POST api/order
public
Order Post(Order order)
{
return
order;
}
// DELETE api/order/5
public
void
Delete(
int
id)
{
}
}
|
二、WebApi的请求过程
webApi的核心是对消息的管道处理,整个核心是有一系列消息处理器(HttpMessageHandler)首尾连接的双向管道,管道头为HttpServer,管道尾为HttpControllerDispatcher,HttpControllerDispatcher负责对controller的激活和action的执行,然后相应的消息逆向流出管道。
所以我们可以利用HttpMessageInvoker将一个请求消息HttpRequestMessage发送到管道中,最后收到的消息HttpResponseMessage就代表一个真实的请求响应。
三、Get请求的测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[Test]
public
void
GetTest()
{
HttpConfiguration config =
new
HttpConfiguration();
WebApiConfig.Register(config);
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
HttpServer server =
new
HttpServer(config);
HttpMessageInvoker messageInvoker =
new
HttpMessageInvoker(server);
CancellationTokenSource cts =
new
CancellationTokenSource();
HttpRequestMessage request =
new
HttpRequestMessage(HttpMethod.Get, baseAddress +
"api/order"
);
using
(HttpResponseMessage response = messageInvoker.SendAsync(request, cts.Token).Result)
{
var
content = response.Content.ReadAsStringAsync().Result;
var
result = JsonConvert.DeserializeObject<Order>(content);
result.Name.Should().Be(
"name"
);
}
}
|
四、Post请求的测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
[Test]
public
void
PostTest()
{
HttpConfiguration config =
new
HttpConfiguration();
WebApiConfig.Register(config);
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
HttpServer server =
new
HttpServer(config);
HttpMessageInvoker messageInvoker =
new
HttpMessageInvoker(server);
CancellationTokenSource cts =
new
CancellationTokenSource();
HttpRequestMessage request =
new
HttpRequestMessage(HttpMethod.Post, baseAddress +
"api/order"
);
var
order =
new
Order() { Id = 1, Name =
"orderName"
, Descriptions =
"orderDescriptions"
};
request.Content =
new
ObjectContent<Order>(order,
new
JsonMediaTypeFormatter());
using
(HttpResponseMessage response = messageInvoker.SendAsync(request, cts.Token).Result)
{
var
content = JsonConvert.SerializeObject(order,
new
JsonSerializerSettings() { ContractResolver =
new
CamelCasePropertyNamesContractResolver() });
response.Content.ReadAsStringAsync().Result.Should().Be(content);
}
}
|
四、重构
可以看到这两个测试大部分的代码是相同的,都是用来发送请求。因此我们提取一个webApiTestBase类,该基类可以提供InvokeGetRequest,InvokePostRequest,InvokePutRequest等方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
public
abstract
class
ApiTestBase
{
public
abstract
string
GetBaseAddress();
protected
TResult InvokeGetRequest<TResult>(
string
api)
{
using
(
var
invoker = CreateMessageInvoker())
{
using
(
var
cts =
new
CancellationTokenSource())
{
var
request =
new
HttpRequestMessage(HttpMethod.Get, GetBaseAddress() + api);
using
(HttpResponseMessage response = invoker.SendAsync(request, cts.Token).Result)
{
var
result = response.Content.ReadAsStringAsync().Result;
return
JsonConvert.DeserializeObject<TResult>(result);
}
}
}
}
protected
TResult InvokePostRequest<TResult, TArguemnt>(
string
api, TArguemnt arg)
{
var
invoker = CreateMessageInvoker();
using
(
var
cts =
new
CancellationTokenSource())
{
var
request =
new
HttpRequestMessage(HttpMethod.Post, GetBaseAddress() + api);
request.Content =
new
ObjectContent<TArguemnt>(arg,
new
JsonMediaTypeFormatter());
using
(HttpResponseMessage response = invoker.SendAsync(request, cts.Token).Result)
{
var
result = response.Content.ReadAsStringAsync().Result;
return
JsonConvert.DeserializeObject<TResult>(result);
}
}
}
private
HttpMessageInvoker CreateMessageInvoker()
{
var
config =
new
HttpConfiguration();
WebApiConfig.Register(config);
var
server =
new
HttpServer(config);
var
messageInvoker =
new
HttpMessageInvoker(server);
return
messageInvoker;
}
}
|
有了这个基类,我们写测试只需要重写方法GetBaseAddress(),然后直接调用基类方法并进行断言即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
[TestFixture]
public
class
OrderApiTests:ApiTestBase
{
public
override
string
GetBaseAddress()
{
}
[Test]
public
void
Should_get_order_successfully()
{
var
result = InvokeGetRequest<Order>(
"api/order"
);
result.Name.Should().Be(
"name"
);
result.Descriptions.Should().Be(
"descriptions"
);
result.Id.Should().Be(1);
}
[Test]
public
void
Should_post_order_successfully()
{
var
newOrder=
new
Order(){Name =
"newOrder"
,Id = 100,Descriptions =
"new-order-description"
};
var
result = InvokePostRequest<Order,Order>(
"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启动起来,然后。。。麻烦
缺点:我觉得原文作者说的那些缺点都可以忽略不计^_^