代码改变世界

【MVC5】First Unit Test

2015-11-09 14:52  佳佳的博客  阅读(593)  评论(0编辑  收藏  举报

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版)》