单元测试 ASP.NET Web API 2

作者 Tom FitzMacken

下载已完成项目

本指南和应用程序演示如何为 Web API 2 应用程序创建简单的单元测试。 本教程演示如何在解决方案中包含单元测试项目,以及如何编写检查控制器方法返回的值的测试方法。

本教程假定你熟悉 ASP.NET Web API的基本概念。 有关介绍性教程,请参阅使用 ASP.NET Web API 2 入门

本主题中的单元测试有意限制为简单数据方案。 有关单元测试更高级的数据方案,请参阅在单元测试 ASP.NET Web API 2 时模拟实体框架

本教程中使用的软件版本

本主题内容

本主题包含以下各节:

先决条件

Visual Studio 2017 社区版、专业版或企业版

下载代码

下载 已完成的项目。 可下载的项目包括本主题的单元测试代码,以及当单元测试 ASP.NET Web API主题时模拟实体框架的单元测试代码。

使用单元测试项目创建应用程序

可以在创建应用程序时创建单元测试项目,也可以将单元测试项目添加到现有应用程序。 本教程介绍用于创建单元测试项目的两种方法。 若要遵循本教程,可以使用任一方法。

创建应用程序时添加单元测试项目

创建名为 StoreApp 的新 ASP.NET Web 应用程序。

创建项目

在“新建 ASP.NET 项目”窗口中,选择 “空 ”模板并为 Web API 添加文件夹和核心引用。 选择 “添加单元测试 ”选项。 单元测试项目自动命名为 StoreApp.Tests。 可以保留此名称。

创建单元测试项目

创建应用程序后,会看到它包含两个项目。

两个项目

将单元测试项目添加到现有应用程序

如果在创建应用程序时未创建单元测试项目,可以随时添加一个。 例如,假设已有一个名为 StoreApp 的应用程序,并且想要添加单元测试。 若要添加单元测试项目,请右键单击解决方案,然后选择 “添加 ”和“ 新建项目”。

将新项目添加到解决方案

在左窗格中选择“ 测试 ”,并为项目类型选择“ 单元测试 项目”。 将项目命名为 StoreApp.Tests

添加单元测试项目

你将在解决方案中看到单元测试项目。

在单元测试项目中,添加对原始项目的项目引用。

设置 Web API 2 应用程序

在 StoreApp 项目中,将类文件添加到名为 Product.csModels 文件夹。 将文件的内容替换为以下代码。

using System;

namespace StoreApp.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

生成解决方案。

右键单击“控制器”文件夹,然后选择 “添加 ”和“ 新建基架项”。 选择“ Web API 2 控制器 - 空”。

添加新控制器

将控制器名称设置为 SimpleProductController,然后单击“ 添加”。

指定控制器

用下面的代码替换现有代码。 为了简化此示例,数据存储在列表中,而不是存储在数据库中。 此类中定义的列表表示生产数据。 请注意,控制器包含一个构造函数,该构造函数将 Product 对象的列表作为参数。 通过此构造函数,可以在单元测试时传递测试数据。 控制器还包括两 个异步 方法,用于说明单元测试异步方法。 这些异步方法是通过调用 Task.FromResult 实现的,以最大程度地减少无关代码,但通常这些方法将包括资源密集型操作。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
using StoreApp.Models;

namespace StoreApp.Controllers
{
    public class SimpleProductController : ApiController
    {
        List<Product> products = new List<Product>();        
           
        public SimpleProductController() { }

        public SimpleProductController(List<Product> products)
        {
            this.products = products;
        }

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public async Task<IEnumerable<Product>> GetAllProductsAsync()
        {
            return await Task.FromResult(GetAllProducts());
        }

        public IHttpActionResult GetProduct(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }

        public async Task<IHttpActionResult> GetProductAsync(int id)
        {
            return await Task.FromResult(GetProduct(id));
        }
    }
}

GetProduct 方法返回 IHttpActionResult 接口的实例。 IHttpActionResult 是 Web API 2 中的新功能之一,它简化了单元测试开发。 实现 IHttpActionResult 接口的类位于 System.Web.Http.Results 命名空间中。 这些类表示来自操作请求的可能响应,它们对应于 HTTP 状态代码。

生成解决方案。

现在可以设置测试项目。

在测试项目中安装 NuGet 包

使用空模板创建应用程序时, (StoreApp.Tests) 单元测试项目不包含任何已安装的 NuGet 包。 其他模板(如 Web API 模板)在单元测试项目中包括一些 NuGet 包。 在本教程中,必须将 Microsoft ASP.NET Web API 2 核心包包含在测试项目中。

右键单击 StoreApp.Tests 项目,然后选择“ 管理 NuGet 包”。 必须选择 StoreApp.Tests 项目才能将包添加到该项目。

管理包

查找并安装 Microsoft ASP.NET Web API 2 核心包。

安装 Web API 核心包

关闭“管理 NuGet 包”窗口。

创建测试

默认情况下,测试项目包含名为 UnitTest1.cs 的空测试文件。 此文件显示用于创建测试方法的属性。 对于单元测试,可以使用此文件或创建自己的文件。

UnitTest1

在本教程中,你将创建自己的测试类。 可以删除 UnitTest1.cs 文件。 添加名为 TestSimpleProductController.cs 的类,并将代码替换为以下代码。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Http.Results;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using StoreApp.Controllers;
using StoreApp.Models;

namespace StoreApp.Tests
{
    [TestClass]
    public class TestSimpleProductController
    {
        [TestMethod]
        public void GetAllProducts_ShouldReturnAllProducts()
        {
            var testProducts = GetTestProducts();
            var controller = new SimpleProductController(testProducts);

            var result = controller.GetAllProducts() as List<Product>;
            Assert.AreEqual(testProducts.Count, result.Count);
        }

        [TestMethod]
        public async Task GetAllProductsAsync_ShouldReturnAllProducts()
        {
            var testProducts = GetTestProducts();
            var controller = new SimpleProductController(testProducts);

            var result = await controller.GetAllProductsAsync() as List<Product>;
            Assert.AreEqual(testProducts.Count, result.Count);
        }

        [TestMethod]
        public void GetProduct_ShouldReturnCorrectProduct()
        {
            var testProducts = GetTestProducts();
            var controller = new SimpleProductController(testProducts);

            var result = controller.GetProduct(4) as OkNegotiatedContentResult<Product>;
            Assert.IsNotNull(result);
            Assert.AreEqual(testProducts[3].Name, result.Content.Name);
        }

        [TestMethod]
        public async Task GetProductAsync_ShouldReturnCorrectProduct()
        {
            var testProducts = GetTestProducts();
            var controller = new SimpleProductController(testProducts);

            var result = await controller.GetProductAsync(4) as OkNegotiatedContentResult<Product>;
            Assert.IsNotNull(result);
            Assert.AreEqual(testProducts[3].Name, result.Content.Name);
        }

        [TestMethod]
        public void GetProduct_ShouldNotFindProduct()
        {
            var controller = new SimpleProductController(GetTestProducts());

            var result = controller.GetProduct(999);
            Assert.IsInstanceOfType(result, typeof(NotFoundResult));
        }

        private List<Product> GetTestProducts()
        {
            var testProducts = new List<Product>();
            testProducts.Add(new Product { Id = 1, Name = "Demo1", Price = 1 });
            testProducts.Add(new Product { Id = 2, Name = "Demo2", Price = 3.75M });
            testProducts.Add(new Product { Id = 3, Name = "Demo3", Price = 16.99M });
            testProducts.Add(new Product { Id = 4, Name = "Demo4", Price = 11.00M });

            return testProducts;
        }
    }
}

运行测试

现在可以运行测试了。 将测试使用 TestMethod 属性标记的所有方法。 在“ 测试 ”菜单项中,运行测试。

运行测试

打开“ 测试资源管理器” 窗口,并注意测试结果。

测试结果

总结

您已完成本教程的学习。 本教程中的数据经过特意简化,侧重于单元测试条件。 有关单元测试更高级的数据方案,请参阅在单元测试 ASP.NET Web API 2 时模拟实体框架