为 ASP.NET MVC 应用程序创建单元测试 (C#)

作者 :Stephen Walther

了解如何为控制器操作创建单元测试。 在本教程中,Stephen Walther 演示如何测试控制器操作是返回特定视图、返回一组特定的数据,还是返回不同类型的操作结果。

本教程的目的是演示如何为 ASP.NET MVC 应用程序中的控制器编写单元测试。 我们将讨论如何生成三种不同类型的单元测试。 你将了解如何测试控制器操作返回的视图、如何测试控制器操作返回的视图数据,以及如何测试一个控制器操作是否将你重定向到第二个控制器操作。

创建受测控制器

首先创建要测试的控制器。 名为 ProductController的控制器包含在清单 1 中。

列表 1 – ProductController.cs

using System;
using System.Web.Mvc;

namespace Store.Controllers
{
     public class ProductController : Controller
     {
          public ActionResult Index()
          {
               // Add action logic here
               throw new NotImplementedException();
          }

          public ActionResult Details(int Id)
          {

               return View("Details");
          }
     }
}

包含 ProductController 两个名为 Index()Details()的操作方法。 这两种操作方法都返回视图。 请注意,操作 Details() 接受名为 Id 的参数。

测试控制器返回的视图

假设我们要测试 是否 ProductController 返回正确的视图。 我们希望确保在调用操作时 ProductController.Details() 返回“详细信息”视图。 清单 2 中的测试类包含一个单元测试,用于测试操作返回的 ProductController.Details() 视图。

清单 2 – ProductControllerTest.cs

using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Store.Controllers;

namespace StoreTests.Controllers
{
     [TestClass]
     public class ProductControllerTest
     {
          [TestMethod]
          public void TestDetailsView()
          {
               var controller = new ProductController();
               var result = controller.Details(2) as ViewResult;
               Assert.AreEqual("Details", result.ViewName);

          }
     }
}

清单 2 中的 类包括名为 的测试 TestDetailsView()方法。 此方法包含三行代码。 第一行代码创建 类的新实例 ProductController 。 第二行代码调用控制器的操作 Details() 方法。 最后,最后一行代码检查操作返回的 Details() 视图是否为“详细信息”视图。

属性 ViewResult.ViewName 表示控制器返回的视图的名称。 有关测试此属性的一个大警告。 控制器可通过两种方式返回视图。 控制器可以显式返回如下所示的视图:

public ActionResult Details(int Id)
{
     return View("Details");
}

或者,可以从控制器操作的名称推断视图的名称,如下所示:

public ActionResult Details(int Id)
{
     return View();
}

此控制器操作还返回名为 的 Details视图。 但是,视图的名称是从操作名称推断出来的。 如果要测试视图名称,则必须从控制器操作中显式返回视图名称。

可以通过输入键盘组合 Ctrl-R、A 或单击“ 在解决方案中运行所有测试 ”按钮来运行清单 2 中的单元测试 (请参阅图 1) 。 如果测试通过,你将在图 2 中看到“测试结果”窗口。

在解决方案中运行所有测试

图 01:在解决方案中运行所有测试 (单击以查看全尺寸图像)

成功!

图 02:成功! (单击以查看全尺寸图像)

测试控制器返回的视图数据

MVC 控制器使用名为 View Data的内容将数据传递到视图。 例如,假设你想要在调用 ProductController Details() 操作时显示特定产品的详细信息。 在这种情况下,可以创建在模型中定义的类的实例Product () ,并通过利用 View Data将该实例传递给Details视图。

清单 3 中修改的 ProductController 包括返回 Product 的更新 Details() 操作。

清单 3 – ProductController.cs

using System;
using System.Web.Mvc;

namespace Store.Controllers
{
     public class ProductController : Controller
     {
          public ActionResult Index()
          {
               // Add action logic here
               throw new NotImplementedException();
          }

          public ActionResult Details(int Id)
          {
               var product = new Product(Id, "Laptop");
               return View("Details", product);
          }
     }
}

首先,该 Details() 操作创建表示笔记本电脑的 Product 类的新实例。 接下来,类的 Product 实例作为第二个参数传递给 View() 方法。

可以编写单元测试来测试预期数据是否包含在视图数据中。 清单 4 中的单元测试测试在调用 ProductController Details() 操作方法时是否返回表示笔记本电脑的产品。

清单 4 – ProductControllerTest.cs

using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Store.Controllers;

namespace StoreTests.Controllers
{
     [TestClass]
     public class ProductControllerTest
     {

          [TestMethod]
          public void TestDetailsViewData()
          {
               var controller = new ProductController();
               var result = controller.Details(2) as ViewResult;
               var product = (Product) result.ViewData.Model;
               Assert.AreEqual("Laptop", product.Name);
          }
     }
}

在清单 4 中 TestDetailsView() ,方法通过调用 方法测试返回的 Details() 视图数据。 ViewData通过调用 Details() 方法,在返回的 上ViewResult公开为 属性。 属性 ViewData.Model 包含传递给视图的产品。 该测试只是验证“查看数据”中包含的产品是否名为 Laptop。

测试控制器返回的操作结果

更复杂的控制器操作可能会返回不同类型的操作结果,具体取决于传递给控制器操作的参数的值。 控制器操作可以返回各种类型的操作结果, ViewResult包括 、 RedirectToRouteResultJsonResult

例如,当您将有效的产品 ID 传递给操作Details时,清单 5 中修改的操作Details()将返回视图。 如果传递的产品 ID(值小于 1 的 ID)无效,则会重定向到操作 Index()

列表 5 – ProductController.cs

using System;
using System.Web.Mvc;
namespace Store.Controllers
{
     public class ProductController : Controller
     {
          public ActionResult Index()
          {
               // Add action logic here
               throw new NotImplementedException();
          }
          public ActionResult Details(int Id)
          {
               if (Id < 1)
                    return RedirectToAction("Index");
               var product = new Product(Id, "Laptop");
               return View("Details", product);

          }
     }
}

可以使用清单 6 中的单元测试来测试操作的行为 Details() 。 清单 6 中的单元测试验证在将值为 -1 的 ID 传递给 方法时,是否已重定向 IndexDetails() 视图。

清单 6 – ProductControllerTest.cs

using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Store.Controllers;
namespace StoreTests.Controllers
{
     [TestClass]
     public class ProductControllerTest
     {
          [TestMethod]
          public void TestDetailsRedirect()
          {
               var controller = new ProductController();
               var result = (RedirectToRouteResult) controller.Details(-1);
               Assert.AreEqual("Index", result.Values["action"]);

          }
     }
}

在控制器操作中调用 RedirectToAction() 方法时,控制器操作将 RedirectToRouteResult返回 。 测试将检查 是否 RedirectToRouteResult 将用户重定向到名为 的 Index控制器操作。

总结

本教程介绍了如何为 MVC 控制器操作生成单元测试。 首先,你了解了如何验证控制器操作是否返回了正确的视图。 你已了解如何使用 ViewResult.ViewName 属性来验证视图的名称。

接下来,我们研究了如何测试 的内容 View Data。 你已了解如何检查调用控制器操作后是否返回了View Data正确的产品。

最后,我们讨论了如何测试是否从控制器操作返回不同类型的操作结果。 你已了解如何测试控制器是返回 ViewResult 还是 RedirectToRouteResult