ASP.NET Core 对Controller进行单元测试

单元测试对我们的代码质量非常重要。很多同学都会对业务逻辑或者工具方法写测试用例,但是往往忽略了对Controller层写单元测试。我所在的公司没见过一个对Controller写过测试的。今天来演示下如果对Controller进行单元测试。以下内容默认您对单元测试有所了解,比如如何mock一个接口。在这里多叨叨一句,面向接口的好处,除了能够快速的替换实现类(其实大部分接口不会有多个实现),最大的好处就是可以进行mock,可以进行单元测试。

测试Action

下面的Action非常简单,非常常见的一种代码。根据用户id去获取用户信息然后展示出来。下面看看如何对这个Action进行测试。

   public class UserController : Controller{private readonly IUserService _userService;public UserController(IUserService userService){_userService = userService;}public IActionResult UserInfo(string userId){if (string.IsNullOrEmpty(userId)){throw new ArgumentNullException(nameof(userId));}var user = _userService.Get(userId);return View(user);}}

测试代码:

  [TestMethod()]public void UserInfoTest(){var userService = new Mock<IUserService>();userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User());var ctrl = new UserController(userService.Object);//对空参数进行assertAssert.ThrowsException<ArgumentNullException>(() => {var result = ctrl.UserInfo(null);});//对空参数进行assertAssert.ThrowsException<ArgumentNullException>(() => {var result = ctrl.UserInfo("");});var result = ctrl.UserInfo("1");Assert.IsNotNull(result);Assert.IsInstanceOfType(result, typeof(ViewResult));}

我们对一个Action进行测试主要的思路就是模拟各种入参,使测试代码能够到达所有的分支,并且Assert输出是否为空,是否为指定的类型等。

对ViewModel进行测试

我们编写Action的时候还会涉及ViewModel给视图传递数据,这部分也需要进行测试。修改测试用例,加入对ViewModel的测试代码:

  [TestMethod()]public void UserInfoTest(){var userService = new Mock<IUserService>();userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User(){Id = "x"}) ;var ctrl = new UserController(userService.Object);Assert.ThrowsException<ArgumentNullException>(() => {var result = ctrl.UserInfo(null);});Assert.ThrowsException<ArgumentNullException>(() => {var result = ctrl.UserInfo("");});var result = ctrl.UserInfo("1");Assert.IsNotNull(result);Assert.IsInstanceOfType(result, typeof(ViewResult));//对viewModel进行assertvar vr = result as ViewResult;Assert.IsNotNull(vr.Model);Assert.IsInstanceOfType(vr.Model, typeof(User));var user = vr.Model as User;Assert.AreEqual("x", user.Id);}

对ViewData进行测试

我们编写Action的时候还会涉及ViewData给视图传递数据,这部分同样需要测试。修改Action代码,对ViewData进行赋值:

   public IActionResult UserInfo(string userId){if (string.IsNullOrEmpty(userId)){throw new ArgumentNullException(nameof(userId));}var user = _userService.Get(userId);ViewData["title"] = "user_info";return View(user);}

修改测试用例,加入对ViewData的测试代码:

   [TestMethod()]public void UserInfoTest(){var userService = new Mock<IUserService>();userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User(){Id = "x"}) ;var ctrl = new UserController(userService.Object);Assert.ThrowsException<ArgumentNullException>(() => {var result = ctrl.UserInfo(null);});Assert.ThrowsException<ArgumentNullException>(() => {var result = ctrl.UserInfo("");});var result = ctrl.UserInfo("1");Assert.IsNotNull(result);Assert.IsInstanceOfType(result, typeof(ViewResult));var vr = result as ViewResult;Assert.IsNotNull(vr.Model);Assert.IsInstanceOfType(vr.Model, typeof(User));var user = vr.Model as User;Assert.AreEqual("x", user.Id);//对viewData进行assertAssert.IsTrue(vr.ViewData.ContainsKey("title"));var title = vr.ViewData["title"];Assert.AreEqual("user_info", title);}

对ViewBag进行测试

因为ViewBag事实上是ViewData的dynamic类型的包装,所以Action代码不用改,可以直接对ViewBag进行测试:

     [TestMethod()]public void UserInfoTest(){var userService = new Mock<IUserService>();userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User(){Id = "x"}) ;var ctrl = new UserController(userService.Object);Assert.ThrowsException<ArgumentNullException>(() => {var result = ctrl.UserInfo(null);});Assert.ThrowsException<ArgumentNullException>(() => {var result = ctrl.UserInfo("");});var result = ctrl.UserInfo("1");Assert.IsNotNull(result);Assert.IsInstanceOfType(result, typeof(ViewResult));var vr = result as ViewResult;Assert.IsNotNull(vr.Model);Assert.IsInstanceOfType(vr.Model, typeof(User));var user = vr.Model as User;Assert.AreEqual("x", user.Id);Assert.IsTrue(vr.ViewData.ContainsKey("title"));var title = vr.ViewData["title"];Assert.AreEqual("user_info", title);//对viewBag进行assertstring title1 = ctrl.ViewBag.title;Assert.AreEqual("user_info", title1);}

设置HttpContext

我们编写Action的时候很多时候需要调用基类里的HttpContext,比如获取Request对象,获取Path,获取Headers等等,所以有的时候需要自己实例化HttpContext以进行测试。

    var ctrl = new AccountController();ctrl.ControllerContext = new ControllerContext();ctrl.ControllerContext.HttpContext = new DefaultHttpContext();

对HttpContext.SignInAsync进行mock

我们使用ASP.NET Core框架进行登录认证的时候,往往使用HttpContext.SignInAsync进行认证授权,所以单元测试的时候也需要进行mock。下面是一个典型的登录Action,对密码进行认证后调用SignInAsync在客户端生成登录凭证,否则跳到登录失败页面。

   public async Task<IActionResult> Login(string password){if (password == "123"){var claims = new List<Claim>{new Claim("UserName","x")};var authProperties = new AuthenticationProperties{};var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimsIdentity),authProperties);return Redirect("login_success");}return Redirect("login_fail");}

HttpContext.SignInAsync其实个时扩展方法,SignInAsync其实最终是调用了IAuthenticationService里的SignInAsync方法。所以我们需要mock的就是IAuthenticationService接口,否者代码走到HttpContext.SignInAsync会提示找不到IAuthenticationService的service。而IAuthenticationService本身是通过IServiceProvider注入到程序里的,所以同时需要mock接口IServiceProvider。

    [TestMethod()]public async Task LoginTest(){var ctrl = new AccountController();var authenticationService = new Mock<IAuthenticationService>();var sp = new Mock<IServiceProvider>();sp.Setup(s => s.GetService(typeof(IAuthenticationService))).Returns(() => {return authenticationService.Object;});ctrl.ControllerContext = new ControllerContext();ctrl.ControllerContext.HttpContext = new DefaultHttpContext();ctrl.ControllerContext.HttpContext.RequestServices = sp.Object;var result = await ctrl.Login("123");Assert.IsNotNull(result);Assert.IsInstanceOfType(result, typeof(RedirectResult));var rr = result as RedirectResult;Assert.AreEqual("login_success", rr.Url);result = await ctrl.Login("1");Assert.IsNotNull(result);Assert.IsInstanceOfType(result, typeof(RedirectResult));rr = result as RedirectResult;Assert.AreEqual("login_fail", rr.Url);}

对HttpContext.AuthenticateAsync进行mock

HttpContext.AuthenticateAsync同样比较常用。这个扩展方法同样是在IAuthenticationService里,所以测试代码跟上面的SignInAsync类似,只是需要对AuthenticateAsync继续mock返回值success or fail。

     public async Task<IActionResult> Login(){if ((await HttpContext.AuthenticateAsync()).Succeeded){return Redirect("/home");}return Redirect("/login");}

测试用例:

        [TestMethod()]public async Task LoginTest1(){var authenticationService = new Mock<IAuthenticationService>();//设置AuthenticateAsync为successauthenticationService.Setup(s => s.AuthenticateAsync(It.IsAny<HttpContext>(), It.IsAny<string>())).ReturnsAsync(AuthenticateResult.Success(new AuthenticationTicket(new System.Security.Claims.ClaimsPrincipal(), "")));var sp = new Mock<IServiceProvider>();sp.Setup(s => s.GetService(typeof(IAuthenticationService))).Returns(() => {return authenticationService.Object;});var ctrl = new AccountController();ctrl.ControllerContext = new ControllerContext();ctrl.ControllerContext.HttpContext = new DefaultHttpContext();ctrl.ControllerContext.HttpContext.RequestServices = sp.Object;var act = await ctrl.Login();Assert.IsNotNull(act);Assert.IsInstanceOfType(act, typeof(RedirectResult));var rd = act as RedirectResult;Assert.AreEqual("/home", rd.Url);//设置AuthenticateAsync为failauthenticationService.Setup(s => s.AuthenticateAsync(It.IsAny<HttpContext>(), It.IsAny<string>())).ReturnsAsync(AuthenticateResult.Fail(""));act = await ctrl.Login();Assert.IsNotNull(act);Assert.IsInstanceOfType(act, typeof(RedirectResult));rd = act as RedirectResult;Assert.AreEqual("/login", rd.Url);}

Filter进行测试

我们写Controller的时候往往需要配合很多Filter使用,所以Filter的测试也很重要。下面演示下如何对Fitler进行测试。

    public class MyFilter: ActionFilterAttribute{public override void OnActionExecuting(ActionExecutingContext context){if (context.HttpContext.Request.Path.Value.Contains("/abc/")){context.Result = new ContentResult() {Content = "拒绝访问"};}base.OnActionExecuting(context);}}

对Filter的测试最主要的是模拟ActionExecutingContext参数,以及其中的HttpContext等,然后对预期进行Assert。

       [TestMethod()]public void OnActionExecutingTest(){var filter = new MyFilter();var actContext = new ActionContext(new DefaultHttpContext(),new RouteData(), new ActionDescriptor());actContext.HttpContext.Request.Path = "/abc/123";var listFilters = new List<IFilterMetadata>();var argDict = new Dictionary<string, object>();var actExContext = new ActionExecutingContext(actContext ,listFilters ,argDict ,new AccountController());filter.OnActionExecuting(actExContext);Assert.IsNotNull(actExContext.Result);Assert.IsInstanceOfType(actExContext.Result, typeof(ContentResult));var cr = actExContext.Result as ContentResult;Assert.AreEqual("拒绝访问", cr.Content);actContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());actContext.HttpContext.Request.Path = "/1/123";listFilters = new List<IFilterMetadata>();argDict = new Dictionary<string, object>();actExContext = new ActionExecutingContext(actContext,listFilters,argDict,new AccountController());filter.OnActionExecuting(actExContext);Assert.IsNull(actExContext.Result);}

关注我的公众号一起玩转技术

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

12对胸椎对应体表标志_铁路信号之信号表示器及标志(三)

信 号 标 志1.信号标志&#xff0c;设在列车运行方向左侧&#xff08;警冲标除外&#xff09;。双线区段的轨道电路调谐区标志设在线路外侧。&#xff08;1&#xff09;警冲标&#xff1a;设在两会合线路线间距离为4 m的中间。线间距离不足4m时&#xff0c;设在两线路中心线最大…

计算机技术题目,计算机技术题目.doc

计算机技术题目一、单项选择题(共10小题&#xff0c;每小题2分)1.下列选项不是生物识别中的行为特征的是() A.?虹膜 B.?声音 C.?签字 D.?步态2.下列关于云计算技术描述&#xff0c;错误的是() A.?云计算技术是分布式计算技术的一种 B.?搜索引擎是云计算技术的应用之一 C.…

python 画树 递归_python递归函数绘制分形树的方法

分形几何学的基本思想&#xff1a;客观事物具有自相似性的层次结构&#xff0c;局部和整体在形态&#xff0c;功能&#xff0c;信息&#xff0c;时间&#xff0c;空间等方面具有统计意义上的相似性&#xff0c;称为自相似性&#xff0c;自相似性是指局部是整体成比例缩小的性质…

mui 时间样式错乱_微信编辑器样式排版错位怎么回事?

有小伙伴反映在使用365编辑器时遇到素材样式无法正常使用的情况&#xff0c;出现排版错乱的情况&#xff0c;今天猫头鹰针对这个问题写了一篇避坑指南&#xff0c;或许能帮到小伙伴们~365微信编辑器样式排版错位怎么回事&#xff1f;这种情况的发生其实是排版过程中的错误操作导…

CISCO路由器安全配置

hostname Router1 &#xff1b;路由器名称 enable secret xxxx &#xff1b;特权访问口令为 xxxx interface serial 0 &#xff1b;定义接口 deion To Internet &#xff1b; 目的描述 ip address 162.70.73.33 255.255.255.248 &#xff1b;设置IP地址 ip access-list 101 in …

海德汉编程详细手册_UG编程海德汉系统螺旋铣孔最后一刀欠切解决方案

&#xfeff; 提示&#xff1a;点击上方"NX网"↑ 免费订阅 关注老叶今天给大家分享一个海德汉螺旋铣孔最后一圈不加工&#xff1a;新建一个模型&#xff0c;只要是深度不能整除螺距&#xff0c;都会存在这样的问题OK 新建一个默认后处理用来测试最后一圈确实存在问题…

软件测试基础知识bbst,摘自James Bach对软件测试新手的建议

Automated? Manual? There is no such thing as manual or automated testing. It’s all just testing. Testing is often supported by tools that attempt to simulate user interaction with the system. This is what people call “test automation” even though it i…

python实例方法、类方法、静态方法的区别_Python 实例方法、类方法、静态方法的区别与作用...

Python 实例方法、类方法、静态方法的区别与作用 一、总结 一句话总结&#xff1a; 实例方法&#xff1a;第一个参数必须是实例对象&#xff0c;该参数名一般约定为“self”&#xff0c;通过它来传递实例的属性和方法&#xff08;也可以传类的属性和方法&#xff09; 类方法&am…

逝者如斯,且听我胡说八道

【导读】嗯、转瞬即逝&#xff0c;还未来得及转身&#xff0c;2020就又这么溜了&#xff0c;溜了&#xff0c;趁其弥留之际&#xff0c;通过小作坊对2020年做个基本总结当我决心要写一篇总结时&#xff0c;我发现我要完了&#xff0c;这可不是一两个小时就能搞定的事情&#xf…

make: *** 没有规则可制作目标“distclean”。 停止。_Makefile伪目标

这一个章节我们主要讲的是 Makefile 中的伪目标。所谓的伪目标可以这样来理解,它并不会创建目标文件,只是想去执行这个目标下面的命令。伪目标的存在可以帮助我们找到命令并执行。使用伪目标有两点原因: 避免我们的 Makefile 中定义的只执行的命令的目标和工作目录下的实际文…

Lighttpd

Lighttpd Lighttpd是一个新兴的、轻量级的 web 服务器&#xff0c;它开始越来越多的应用在一些重要场合&#xff0c;如&#xff1a;YouTobe、Sourceforge、豆瓣…… Lighttpd 以安全、快速和内存消耗低著称&#xff0c;还专门为大型分布式连接环境做了优化&#xff0c;支持 Fas…

汇总:2017 年 IT 界最严重的裁员事件

裁员年年都有&#xff0c;今年特别多从微软、Oracle、IBM&#xff0c;到思科、HPE&#xff0c;再到雅虎、stackoverflow&#xff0c;无论是处于转型变革中的老牌巨头&#xff0c;还是日渐成熟的创新型公司&#xff0c;在动荡的科技行业&#xff0c;裁员风波一浪高过一浪&#x…

计算机结构优化,计算机结构与程序优化.ppt

《计算机结构与程序优化.ppt》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《计算机结构与程序优化.ppt(116页珍藏版)》请在人人文库网上搜索。1、计算机结构与程序优化,Introduction to Intel 64 Architectures Optimization,Main Purpose,处理器架构简介 SIMD指令介…

2020 年终总结:变化 积累

这是「进击的Coder」的第 32 篇个人随想作者&#xff1a;崔庆才之前写年终总结都是在每年正月&#xff0c;今年也不例外了。2020 年&#xff0c;对每个人来说都是特殊的一年&#xff0c;突入其来的新冠疫情给大家的生活带来了很大的变化&#xff0c;尤其上班年困难的时候&#…

11尺寸长宽 iphone_新手必知LED显示屏尺寸规格及计算方法

前言&#xff1a;LED屏幕在生活中&#xff0c;随处可见&#xff0c;显示屏、广播屏等等&#xff0c;但是LED尺寸怎么计算的&#xff0c;你知道吗&#xff1f;今天我们一起了解一下LED屏幕尺寸的计算方法。一、点间距的计算1、各单元板常见型号及尺寸LED屏普遍是用单元板做的。L…

正则表达式在python中的应用_学习正则表达式在python中的应用

目的&#xff1a;对文本的处理&#xff0c;正则表达式的功能很强大&#xff0c;可以很巧妙的过滤、匹配、获取想要的字符串&#xff0c;是必须学习的技能&#xff0c;这里只记录常用的写法&#xff0c;详细文档可以参看官方帮助文档。 环境&#xff1a;ubuntu 16.04 python 3.5…

ISA服务器之域内×××用户在外网通过CA验证连接域内×××服务器

一般情况下用户在连接服务器是是要输入密码的&#xff0c;如果这样的话&#xff0c;那么在一些公共场合输入密码的时候很有可能造成密码的泄露&#xff0c;从而造成不必要的损失。口令验证协议虽然硬件上的要求没有质询握手协议那么高&#xff0c;但是&#xff0c;在安全性上面…

高斯、柯西、拉格朗日都还在的话,他们应该最喜欢这个公众号

在现实生活中&#xff0c;你和谁在一起的确很重要&#xff0c;甚至能改变你的成长轨迹&#xff0c;决定你的人生成败。 是否还记得&#xff0c;当你跟学霸做同学的时候&#xff0c;你总会莫名其妙跟他一起撸题目&#xff1b;当宿舍其他兄弟正在打游戏的时候&#xff0c;你也想着…

div css标记,前端初学者必学的div加css标签

原标题&#xff1a;前端初学者必学的div加css标签今天给大家分享前端初学者必须要学习的标签&#xff0c;这些标签你都会了吗&#xff1f;DIV加css标签页头:header登录条:loginBar标志:logo侧栏:sideBar广告:banner导航:nav子导航:subNav菜单:menu子菜单:subMenu搜索:search滚动…