分享

WebAPI的一种单元测试方案

 nisz 2016-06-25
原文出处: richieyangs   

大家是如何对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用来做测试用

C#
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请求的测试

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        [Test]
        public void GetTest()
        {
            string baseAddress = 'http://localhost:33203/';
            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(content);
                result.Name.Should().Be('name');
            }
        }

四、Post请求的测试

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        [Test]
        public void PostTest()
        {
            string baseAddress = 'http://localhost:33203/';
            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, 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等方法

C#
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
    public abstract class ApiTestBase
    {
        public abstract string GetBaseAddress();
        protected TResult InvokeGetRequest(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(result);
                    }
                }
            }
        }
        protected TResult InvokePostRequest(string api, TArguemnt arg)
        {
            var invoker = CreateMessageInvoker();
            using (var cts = new CancellationTokenSource())
            {
                var request = new HttpRequestMessage(HttpMethod.Post, GetBaseAddress() + api);
                request.Content = new ObjectContent(arg, new JsonMediaTypeFormatter());
                using (HttpResponseMessage response = invoker.SendAsync(request, cts.Token).Result)
                {
                    var result = response.Content.ReadAsStringAsync().Result;
                    return JsonConvert.DeserializeObject(result);
                }
            }
        }
        private HttpMessageInvoker CreateMessageInvoker()
        {
            var config = new HttpConfiguration();
            WebApiConfig.Register(config);
            var server = new HttpServer(config);
            var messageInvoker = new HttpMessageInvoker(server);
            return messageInvoker;
        }
    }

有了这个基类,我们写测试只需要重写方法GetBaseAddress(),然后直接调用基类方法并进行断言即可

C#
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
   [TestFixture]
    public class OrderApiTests:ApiTestBase
    {
        public override string GetBaseAddress()
        {
            return 'http://localhost:33203/';
        }
        [Test]
        public void Should_get_order_successfully()
        {
            var result = InvokeGetRequest('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('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启动起来,然后。。。麻烦

缺点:我觉得原文作者说的那些缺点都可以忽略不计。

加入伯乐在线专栏作者。扩大知名度,还能得赞赏!详见《招募专栏作者
2 赞 4 收藏 评论

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多