1.控制器测试
注意点:
1.控制器中不要包含业务逻辑
2.通过构造函数传递服务依赖
例:MathController中有一个Add的Action
using FirstUnitTest.Services; using System.Web.Mvc; namespace FirstUnitTest.Controllers { public class MathController : Controller { IMathService _service; public MathController(IMathService service) { _service = service; } // GET: Math public RedirectToRouteResult Index() { return RedirectToAction("Add"); //return View(); } [HttpGet] public ActionResult Add() { return View(); } [HttpPost] public ViewResult Add(int left, int right) { ViewBag.Result = _service.Add(left, right); return View(); } } }
IMathService定义如下,很显然是个求和方法:
namespace FirstUnitTest.Services { public interface IMathService { int Add(int left, int right); } }
编写一个假Service,给Controller提供一个假的业务逻辑层。
using FirstUnitTest.Services; namespace FirstUnitTest.Tests.Services { public class SpyMathService : IMathService { public int Add_Left; public int Add_Right; public int Add_Result; public int Add(int left, int right) { Add_Left = left; Add_Right = right; return Add_Result; } } }
注意:上面的SpyService中没有对两个值求和,因为我们只关注MathService的Input和Output(Input就是left和right参数,Output就是返回值)。
测试方法:
[TestMethod] public void AddUseMathService() { SpyMathService service = new SpyMathService() { Add_Result = 42 }; MathController controller = new MathController(service); ViewResult result = controller.Add(4, 12); Assert.AreEqual(service.Add_Result, result.ViewBag.Result); Assert.AreEqual(4, service.Add_Left); Assert.AreEqual(12, service.Add_Right); }
Redirect测试(上面MathController中的Index Action):
[TestMethod] public void RedirectToAdd() { SpyMathService service = new SpyMathService(); MathController controller = new MathController(service); RedirectToRouteResult result = controller.Index(); Assert.AreEqual("Add", result.RouteValues["action"]); }
2.路由测试
默认MVC项目的路由如下:
using System.Web.Mvc; using System.Web.Routing; namespace FirstUnitTest { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
测试方法:
using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace FirstUnitTest.Tests.Routes { [TestClass] public class RouteTest { /// <summary> /// 测试IngoreRoute函数调用 /// </summary> [TestMethod] public void RouteForEmbeddedResource() { // Arrange var mockContext = new Mock<HttpContextBase>(); mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath) .Returns("~/handler.axd"); var routes = new RouteCollection(); //MvcApplication.RegisterRoutes(routes); RouteConfig.RegisterRoutes(routes); // Act RouteData routeData = routes.GetRouteData(mockContext.Object); // Assert Assert.IsNotNull(routeData); Assert.IsInstanceOfType(routeData.RouteHandler, typeof(StopRoutingHandler)); } /// <summary> /// 测试MapRoute函数调用 /// </summary> [TestMethod] public void RouteToHomePae() { // Arrange var mockContext = new Mock<HttpContextBase>(); mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath) .Returns("~/"); var routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); // Act RouteData routeData = routes.GetRouteData(mockContext.Object); // Assert Assert.IsNotNull(routeData); Assert.AreEqual("Home", routeData.Values["controller"]); Assert.AreEqual("Index", routeData.Values["action"]); Assert.AreEqual(UrlParameter.Optional, routeData.Values["id"]); } // 不需要对不匹配路由编写测试代码 } }
使用Mock需要安装Moq包(需要设定ProjectName参数,否则会默认安装到Web工程)
Install-Package moq -ProjectName FirstUnitTest.Tests
3.验证测试
Movie模型:
using System.ComponentModel.DataAnnotations; using System.Data.Entity; namespace FirstUnitTest.Models { public class Movie { public int Id { get; set; } [Required] public string Title { get; set; } [Required] [Range(1920, 2015)] public int ReleaseYear { get; set; } public int RunTime { get; set; } } public class MovieDb : DbContext { public DbSet<Movie> Movies { get; set; } } }
属性验证:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using FirstUnitTest.Models; using System.ComponentModel.DataAnnotations; namespace FirstUnitTest.Tests.Validation { [TestClass] public class MovieValidationTest { [TestMethod] public void TitleRequireTest() { System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("zh-cn"); Movie movie = new Movie(); ValidationContext context = new ValidationContext(movie, null, null) { DisplayName = "Title", MemberName = "Title", }; RequiredAttribute validator = new RequiredAttribute(); try { validator.Validate(movie.Title, context); } catch (ValidationException ex) { // 错误消息的语言由当前线程的CurrentUICulture决定 Assert.AreEqual("Title 字段是必需的。", ex.Message); //throw; } } } }
※本文参照《ASP.NET MVC 5高级编程(第5版)》