时间:2021-05-02
前言
单元测试对我们的代码质量非常重要。很多同学都会对业务逻辑或者工具方法写测试用例,但是往往忽略了对Controller层写单元测试。我所在的公司没见过一个对Controller写过测试的。今天来演示下如果对Controller进行单元测试。以下内容默认您对单元测试有所了解,比如如何mock一个接口。在这里多叨叨一句,面向接口的好处,除了能够快速的替换实现类(其实大部分接口不会有多个实现),最大的好处就是可以进行mock,可以进行单元测试。
测试Action
下面的Action非常简单,非常常见的一种代码。根据用户id去获取用户信息然后展示出来。下面看看如何对这个Action进行测试。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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); } }测试代码:
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [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); //对空参数进行assert Assert.ThrowsException<ArgumentNullException>(() => { var result = ctrl.UserInfo(null); }); //对空参数进行assert Assert.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的测试代码:
? 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 [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进行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进行赋值:
? 1 2 3 4 5 6 7 8 9 10 11 12 13 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的测试代码:
对ViewBag进行测试
因为ViewBag事实上是ViewData的dynamic类型的包装,所以Action代码不用改,可以直接对ViewBag进行测试:
设置HttpContext
我们编写Action的时候很多时候需要调用基类里的HttpContext,比如获取Request对象,获取Path,获取Headers等等,所以有的时候需要自己实例化HttpContext以进行测试。
? 1 2 3 var ctrl = new AccountController(); ctrl.ControllerContext = new ControllerContext(); ctrl.ControllerContext.HttpContext = new DefaultHttpContext();对HttpContext.SignInAsync进行mock
我们使用ASP.NET Core框架进行登录认证的时候,往往使用HttpContext.SignInAsync进行认证授权,所以单元测试的时候也需要进行mock。下面是一个典型的登录Action,对密码进行认证后调用SignInAsync在客户端生成登录凭证,否则跳到登录失败页面。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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。
? 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 [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。
? 1 2 3 4 5 6 7 8 9 public async Task<IActionResult> Login() { if ((await HttpContext.AuthenticateAsync()).Succeeded) { return Redirect("/home"); } return Redirect("/login"); }测试用例:
? 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 [TestMethod()] public async Task LoginTest1() { var authenticationService = new Mock<IAuthenticationService>(); //设置AuthenticateAsync为success authenticationService.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为fail authenticationService.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进行测试。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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。
? 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 [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); }总结
到此这篇关于ASP.NET Core对Controller进行单元测试的文章就介绍到这了,更多相关ASP.NET Core对Controller单元测试内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://www.cnblogs.com/kklldog/p/unit-test-core-controller.html
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
为什么要进行单元测试?单元测试保证局部代码的质量单元测试改良项目代码的整体结构单元测试降低测试、维护升级的成本单元测试使开发过程适应频繁变化的需求单元测试有助于
在进行使用的eclipse的进行开发的代码中,必然就会需要进行单元测试,在单元测试的情况提供较多的框架单元测试,例如使用junit单元测试,而在国外进行开发较好
Spring的依赖注入使得我们的代码非常容易进行单元测试——@Controller,@Service,@Entity等注解标注的类基本都是POJO(plaino
由测试部门进行单元测试的成本会很高的,主要原因如下:(1)需多次重复理解程序测试人员进行单元测试时必须理解程序功能甚至代码逻辑;充分的单元测试通常会发现很多细小
测试分类按开发阶段划分单元测试单元测试又称模块测试,针对软件设计中的最小单位——程序模块,进行正确性检查的测试工作。集成测试集成测试又叫组装测试,通常在单元测试