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;设在两线路中心线最大…

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

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

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

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

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

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

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

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

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

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

2020 年终总结:变化 积累

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

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

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

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滚动…

python123动物重量排序_Python爬虫图片学习(一)

Python爬虫学习 一、Python安装与调用 python官网安装地址&#xff1a;https://www.python.org/python帮助手册&#xff1a;在本机的路径C:\Users\Administrator\AppData\Local\Programs\Python\Python37\Doc 创建一个1.py文件&#xff0c;右键打开.py文件&#xff0c;用IDLE方…

activity 防止多次打开_Android开发Activity任务和返回栈

任务是用户在执行某项工作时与之互动的一系列 Activity 的集合。这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。例如&#xff0c;电子邮件应用可能有一个 Activity 来显示新邮件列表。当用户选择一封邮件时&#xff0c;系统会打开一个新的 Activity 来显示…

全程图解:Centos 5.2 下 ORACLE10G 安装配置

1、检查并安装ORACLE必须的软件包在CENTOS上安装Oracle&#xff0c;需要将ORACLE所必须的软件包逐一安装, 参考Oracle安装文档关于Red Hat Enterprise Linux 4.0部分&#xff0c;所需的软件包如下:binutils-2.17.50.0.6-6.el5compat-db-4.2.52-5.1compat-libstdc-33-3.2.3-61co…

提升代码可读性的 10 个技巧

具有较强可读性的代码&#xff0c;能帮助你调试程序&#xff0c;不让自己活得太累。 代码可读性是计算机编程领域中普遍存在的问题。这也是我们成为开发者首先要学习的事情之一。本文会详细介绍在编写强可读性代码时最佳实践中最重要的一部分内容。 1 - 注释和文档 IDE&#xf…

phpsotrm怎么 搜索功能_Windows 10 网络搜索设计太反人类?教你如何彻底关闭它

来源&#xff1a;太平洋电脑网我们知道微软在Windows 10中&#xff0c;特别加强了系统的搜索功能&#xff0c;但Windows 10的搜索的确很难称得上好用。抛开效率低下、呈现结果少、造成系统卡顿等老生常谈的问题不论&#xff0c;在功能设计方面&#xff0c;Windows 10搜索也有硬…

支撑性服务 自动化能力

连载传送门&#xff1a;什么是云原生&#xff1f;云原生设计理念.NET 微服务谈到云原生&#xff0c;绕不开“容器化”Backing services云原生系统依赖于许多不同的辅助资源&#xff0c;例如数据存储、消息队列、监视和身份服务&#xff0c;这些服务统称为支撑性服务。下图显示了…

网站logo放在服务器,自己建网站如何设计网站LOGO

LOGO是一个网站的形象代表或者说是品牌的象征&#xff0c;用户记住了网站LOGO&#xff0c;就相当于记住了网站&#xff0c;因此&#xff0c;自己建网站时要融入网站LOGO的设计&#xff0c;潜移默化地把LOGO形象植入用户脑海中&#xff0c;把网站与LOGO紧密连接在一起&#xff0…

python http协议获取对方的ip地址_http协议(一)基础知识

参考&#xff1a;老张http://link.zhihu.com/?targethttps%3A//www.cnblogs.com/imyalost/p/5627816.html参考书籍——《图解http》当我们在浏览器的地址栏中输入网址&#xff0c;然后点击回车&#xff0c;接着&#xff0c;浏览器就会呈现出我们需要的web界面&#xff0c;那么…