首页 > 网络编程 > ASP.NET > 实用技巧 > ASP.NET Core对Controller单元测试

ASP.NET Core对Controller进行单元测试的完整步骤

作者:Agile.Zhou

这篇文章主要给大家介绍了关于ASP.NET Core对Controller进行单元测试的完整步骤,文中通过示例代码介绍的非常详细,对大家学习或者使用ASP.NET Core具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

单元测试对我们的代码质量非常重要。很多同学都会对业务逻辑或者工具方法写测试用例,但是往往忽略了对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); //对空参数进行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的测试代码:

[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进行赋值:

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进行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<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进行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<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为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进行测试。

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);

到此这篇关于ASP.NET Core对Controller进行单元测试的文章就介绍到这了,更多相关ASP.NET Core对Controller单元测试内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
  • 在 ASP.NET Core 中使用 HTTP 标头传播详情
    在 ASP.NET Core 中使用 HTTP 标头传播详情
    2022-04-04
  • Entity Framework Core对Web项目生成数据库表
    Entity Framework Core对Web项目生成数据库表
    2022-03-03
  • 使用.NET升级助手将.NET Framework项目升级为.NET 6
    使用.NET升级助手将.NET Framework项目升级为.NET 6
    2022-04-04
  • Bootstrap Blazor项目模板安装
    Bootstrap Blazor项目模板安装
    2022-04-04
  • ASP.NET Core MVC控制器请求依赖注入
    ASP.NET Core MVC控制器请求依赖注入
    2022-04-04
  • ASP.NET Core MVC在视图中使用依赖注入
    ASP.NET Core MVC在视图中使用依赖注入
    2022-04-04
  • .NET Core(.NET6)中gRPC使用实践
    .NET Core(.NET6)中gRPC使用实践
    2022-04-04
  • ASP.NET Core MVC中的标签助手(TagHelper)用法
    ASP.NET Core MVC中的标签助手(TagHelper)用法
    2022-04-04
  • 美国设下计谋,用娘炮文化重塑日本,已影响至中国
    美国设下计谋,用娘炮文化重塑日本,已影响至中国
    2021-11-19
  • 时空伴随者是什么意思?时空伴随者介绍
    时空伴随者是什么意思?时空伴随者介绍
    2021-11-09
  • 工信部称网盘企业免费用户最低速率应满足基本下载需求,天翼云盘回应:坚决支持,始终
    工信部称网盘企业免费用户最低速率应满足基本下载需求,天翼云盘回应:坚决支持,始终
    2021-11-05
  • 2022年放假安排出炉:五一连休5天 2022年所有节日一览表
    2022年放假安排出炉:五一连休5天 2022年所有节日一览表
    2021-10-26
  • 电脑版 - 返回首页

    2006-2024 脚本之家 JB51.Net , All Rights Reserved.
    苏ICP备14036222号