博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ASP.NET Core 对Controller进行单元测试
阅读量:4034 次
发布时间:2019-05-24

本文共 12120 字,大约阅读时间需要 40 分钟。

单元测试对我们的代码质量非常重要。很多同学都会对业务逻辑或者工具方法写测试用例,但是往往忽略了对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
(); userService.Setup(s => s.Get(It.IsAny
())).Returns(new User()); var ctrl = new UserController(userService.Object); //对空参数进行assert Assert.ThrowsException
(() => { var result = ctrl.UserInfo(null); }); //对空参数进行assert Assert.ThrowsException
(() => { 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
(); userService.Setup(s => s.Get(It.IsAny
())).Returns(new User() { Id = "x" }) ; var ctrl = new UserController(userService.Object); Assert.ThrowsException
(() => { var result = ctrl.UserInfo(null); }); Assert.ThrowsException
(() => { var result = ctrl.UserInfo(""); }); var result = ctrl.UserInfo("1"); Assert.IsNotNull(result); Assert.IsInstanceOfType(result, typeof(ViewResult)); //对viewModel进行assert 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进行测试

我们编写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
(); userService.Setup(s => s.Get(It.IsAny
())).Returns(new User() { Id = "x" }) ; var ctrl = new UserController(userService.Object); Assert.ThrowsException
(() => { var result = ctrl.UserInfo(null); }); Assert.ThrowsException
(() => { 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进行assert Assert.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
(); userService.Setup(s => s.Get(It.IsAny
())).Returns(new User() { Id = "x" }) ; var ctrl = new UserController(userService.Object); Assert.ThrowsException
(() => { var result = ctrl.UserInfo(null); }); Assert.ThrowsException
(() => { 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进行assert string 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
Login(string password) { if (password == "123") { var claims = new List
{ 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
(); var sp = new Mock
(); 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
Login() { if ((await HttpContext.AuthenticateAsync()).Succeeded) { return Redirect("/home"); } return Redirect("/login"); }

测试用例:

[TestMethod()]        public async Task LoginTest1()        {            var authenticationService = new Mock
(); //设置AuthenticateAsync为success authenticationService.Setup(s => s.AuthenticateAsync(It.IsAny
(), It.IsAny
())) .ReturnsAsync(AuthenticateResult.Success(new AuthenticationTicket(new System.Security.Claims.ClaimsPrincipal(), ""))); var sp = new Mock
(); 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为fail authenticationService.Setup(s => s.AuthenticateAsync(It.IsAny
(), It.IsAny
())) .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
(); var argDict = new Dictionary
(); 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
(); argDict = new Dictionary
(); actExContext = new ActionExecutingContext( actContext, listFilters, argDict, new AccountController() ); filter.OnActionExecuting(actExContext); Assert.IsNull(actExContext.Result); }

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

转载地址:http://xvudi.baihongyu.com/

你可能感兴趣的文章
qt5 everywhere 编译summary
查看>>
qt5 everywhere编译完成后,找不到qmake
查看>>
arm-linux开机读取硬件时钟,设置系统时钟。
查看>>
交叉编译在x86上调试好的qt程序
查看>>
/dev/input/event0 键盘输入
查看>>
qt 创建异形窗体
查看>>
可重入函数与不可重入函数
查看>>
简单Linux C线程池
查看>>
内存池
查看>>
输入设备节点自动生成
查看>>
opencv test code-1
查看>>
eclipse 导入先前存在的项目
查看>>
GNU hello代码分析
查看>>
Qt继电器控制板代码
查看>>
busybox passwd修改密码
查看>>
wpa_supplicant控制脚本
查看>>
rfkill: WLAN hard blocked
查看>>
gstreamer相关工具集合
查看>>
arm 自动升级脚本
查看>>
RS232 四入四出模块控制代码
查看>>