测试 ASP.NET Core API Controller

本文需要您了解ASP.NET Core MVC/Web API, xUnit以及Moq相关知识.

这里有xUnit和Moq的介绍: https://www.cnblogs.com/cgzl/p/9178672.html#test

Controllers可以说是ASP.NET Core MVC/Web API项目的核心, 它们把整个应用都整合到了一起. 可以说Controllers是非常重要的, 所以我们应该对它们做一些测试.

由于我几乎只做API, 所以本文不包括关于MVC功能的测试, 只包括Controller的API相关功能.

测试一个简单的Controller

先举一个简单点的例子:

这个Controller相对简单, 它有一个依赖项.

它一个方法, 返回类型是IActionResult, 又具体分为两种情况.

测试返回结果的类型

首先需要new出来一个被测试的RootController, 标准的叫法叫System Under Test(被测试系统). 它需要一个urlHelper作为依赖项, 那就Mock一个即可.

每组测试数据都会走一遍构造函数的.

该测试方法使用的是Theory, 用了4组数据. 执行方法后返回的结果类型应该实现了IActionResult接口, 这里可以用Assert.IsAssignableFrom<TExpected>(actual)来判断.

注: 为了方便, 我使用了resharper.

测试之前一定要重新Build一下.

然后再点击resharper在方法旁边提供的测试按钮即可:

从图可以看出resharper提供了方便快捷的图标, 在这你可以选择运行或者调试测试.

测试会通过的, Theory下属的4组数据将被视为4个单独的测试:

针对该方法的其它测试

我又添加了两个测试方法, 来测试该方法的不同路径及返回结果:

通常一个测试方法里应该只有一个Assert. 但是第二方法里面有两个Assert, 这是因为这两个Assert都是测试的同一个行为, 所以我认为这样应该是可以的.

Rebuild, 测试:

也是OK的.

看起来针对RootController的GetRoot()方法, 我们好像已经测试了所有可执行的路径. 让我们使用测试代码覆盖率这个功能来确定一下.

点击resharper在测试类旁边提供的CoverAll按钮:

随后会出现单元测试窗口和覆盖率窗口.

直接看覆盖率窗口:

可以看到该Controller和方法的覆盖率都是100%了.

来到被测试的RootController里:

Resharper(实际上是dotCover) 在代码的左边显示出了该行代码是否已经被测试覆盖, 如果都是绿色的就说明都被覆盖了.

导出覆盖率结果

Resharper的代码覆盖率结果可以导出多种格式:

例如导出HTML后也可以查看覆盖率明细:

测试复杂一点的Controller

这个ProductController略微复杂一点, 首先它需要很多依赖项.

看它的POST Action方法, 很多地方需要被测试:

测试ModelState

首先可以测试product为null的情况, 但是这个太简单了, 我就不啰嗦了.

那就测试ModelState.Invalid情况吧:

为了让ModelState Invalid, 我手动添加了ModelState的error. 和被测试方法其它必要的参数.

该方法有三个Assert, 首先判定结果类型是否为UnprocessableEntityObjectResult(422状态码), 然后再判定返回结果包含了ModelState的error.

该测试会pass, 并会覆盖这部分相关的代码:

测试特定方法会被调用

这里需要使用moq了, 为了让被测试方法顺利跑完, 我设定Mock版的UnitOfWork的SaveAsync()方法会返回true, (注意这个方法的返回类型是Task<bool>):

然后通过moq的Verify()方法判定repository的AddProduct()和unitOfWork的SaveAsync()方法分别被调用了.

Build, 测试会pass, 覆盖率目前比较大了(但是覆盖率100%并不能说明代码没问题):

模拟SaveAsync()后的实体数据

该项目使用的是EFCore, 在_unitOfWorkSaveAsync()之后, 变量productModel的Id就会有非0值了, 也就是说productModel在_unitOfWorkSaveAsync()方法执行之后发生了变化.

针对这种情况, 我们可以使用moq的Callback()功能:

刚开始为autoMapper的两次map动作设定了返回值.

然后在UnitOfWork的SaveAsync()执行后有个Callback()回调, 回调时相当于模拟了EFCore的保存, 把最新的值赋给了productModel(看被测试代码), (其实这里不用Callback也行....).

随后就是一系列的Assert, 判定某些方法是否执行, 返回类型是否正确, 返回的数据是否正确等.

Build 测试会通过的:

其它路径的测试

目前该方法还有两处地方没有被覆盖:

可以再写两个测试来覆盖它们:

这两个很简单, 不多介绍了, 注意这里使用了async版本的Assert.Throws().

这两个测试会pass, 最终该方法的代码覆盖率就达到100%了:

ASP.NET Core Web API Controller的测试就介绍这些吧.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏葡萄城控件技术团队

Asp.Net MVC4入门指南(2):添加一个控制器

MVC代表: 模型-视图-控制器 。MVC是一个架构良好并且易于测试和易于维护的开发模式。基于MVC模式的应用程序包含: · Models: 表示该应用程序的数...

1967
来自专栏Golang语言社区

Go语言学习(一):hello world

每学一门语言,程序猿们总是不厌其烦地写一遍hello world,这篇就是Go语言的hello world。 Go语言的环境搭建建议参考《Go We...

3206
来自专栏大内老A

ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法

在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注...

66610
来自专栏大内老A

[WCF权限控制]WCF的三种授权模式

前面的两篇文章(《从两个重要的概念谈起:Identity与Principal[上篇]》和《从两个重要的概念谈起:Identity与Principal[下篇]》)...

2076
来自专栏me的随笔

ASP.NET MVC5 ModelBinder

ASP.NET MVC中,所有的请求最终都会到达某个Controller中的某个Action并由该Action负责具体的处理和响应。为了能够正确处理请求,Act...

1092
来自专栏MasiMaro 的技术博文

PE文件详解(八)

本文转载自小甲鱼PE文件详解系列教程原文传送门 当应用程序需要调用DLL中的函数时,会由系统将DLL中的函数映射到程序的虚拟内存中,dll中本身没有自己的栈...

922
来自专栏青玉伏案

iOS开发之再探多线程编程:Grand Central Dispatch详解

Swift3.0相关代码已在github上更新。之前关于iOS开发多线程的内容发布过一篇博客,其中介绍了NSThread、操作队列以及GCD,介绍的不够深入。今...

1967
来自专栏炉边夜话

JNI使用技巧点滴(二)

作者:normalnotebook 背景<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com...

1742
来自专栏北京马哥教育

python线程笔记

豌豆贴心提醒,本文阅读时间5分钟 来源:伯乐在线 原文:http://python.jobbole.com/87498/ 引言&动机 考虑一下...

3015
来自专栏Danny的专栏

数据库中存储日期的字段类型到底应该用varchar还是datetime ?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

1.3K3

扫码关注云+社区

领取腾讯云代金券