单元测试 ASP.NET Web API 2Unit Testing ASP.NET Web API 2

作者: Tom FitzMackenby Tom FitzMacken

下载完成的项目Download Completed Project

本指南和应用程序演示了如何为 Web API 2 应用程序创建简单的单元测试。This guidance and application demonstrate how to create simple unit tests for your Web API 2 application. 本教程演示如何在解决方案中包括单元测试项目,并编写用于检查控制器方法返回的值的测试方法。This tutorial shows how to include a unit test project in your solution, and write test methods that check the returned values from a controller method.

本教程假设你熟悉 ASP.NET Web API 的基本概念。This tutorial assumes you are familiar with the basic concepts of ASP.NET Web API. 有关介绍性教程,请参阅使用 ASP.NET Web API 2 入门For an introductory tutorial, see Getting Started with ASP.NET Web API 2.

本主题中的单元测试有意限制为简单的数据应用场景。The unit tests in this topic are intentionally limited to simple data scenarios. 有关更高级数据方案的单元测试,请参阅单元测试 ASP.NET Web API 2 时的模拟实体框架For unit testing more advanced data scenarios, see Mocking Entity Framework when Unit Testing ASP.NET Web API 2.

本教程中使用的软件版本Software versions used in the tutorial

主题内容In this topic

本主题包含以下各节:This topic contains the following sections:

系统必备Prerequisites

Visual Studio 2017社区版、专业版或企业版Visual Studio 2017 Community, Professional or Enterprise edition

下载代码Download code

下载完成的项目Download the completed project. 可下载的项目包含本主题的单元测试代码以及单元测试 ASP.NET Web API 主题的模拟实体框架The downloadable project includes unit test code for this topic and for the Mocking Entity Framework when Unit Testing ASP.NET Web API topic.

创建具有单元测试项目的应用程序Create application with unit test project

可在创建应用程序时创建单元测试项目,或者将单元测试项目添加到现有应用程序。You can either create a unit test project when creating your application or add a unit test project to an existing application. 本教程演示创建单元测试项目的两种方法。This tutorial shows both methods for creating a unit test project. 若要按照本教程操作,可以使用这两种方法。To follow this tutorial, you can use either approach.

创建应用程序时添加单元测试项目Add unit test project when creating the application

创建名为StoreApp的新 ASP.NET Web 应用程序。Create a new ASP.NET Web Application named StoreApp.

创建项目

在 "新建 ASP.NET 项目" 窗口中,选择 "" 模板,并为 Web API 添加文件夹和核心引用。In the New ASP.NET Project windows, select the Empty template and add folders and core references for Web API. 选择 "添加单元测试" 选项。Select the Add unit tests option. 单元测试项目被自动命名为StoreAppThe unit test project is automatically named StoreApp.Tests. 您可以保留此名称。You can keep this name.

创建单元测试项目

创建应用程序后,您将看到它包含两个项目。After creating the application, you will see it contains two projects.

两个项目

向现有应用程序添加单元测试项目Add unit test project to an existing application

如果在创建应用程序时未创建单元测试项目,则可以随时添加一个。If you did not create the unit test project when you created your application, you can add one at any time. 例如,假设已有一个名为 StoreApp 的应用程序,并且想要添加单元测试。For example, suppose you already have an application named StoreApp, and you want to add unit tests. 若要添加单元测试项目,请右键单击解决方案,然后选择 "添加" 和 "新建项目"。To add a unit test project, right-click your solution and select Add and New Project.

向解决方案添加新项目

在左窗格中选择 "测试",然后选择 "单元测试项目" 作为 "项目类型"。Select Test in the left pane, and select Unit Test Project for the project type. 将项目命名为StoreAppName the project StoreApp.Tests.

添加单元测试项目

你将在你的解决方案中看到该单元测试项目。You will see the unit test project in your solution.

在单元测试项目中,添加对原始项目的项目引用。In the unit test project, add a project reference to the original project.

设置 Web API 2 应用程序Set up the Web API 2 application

在 StoreApp 项目中,将类文件添加到名为Product.cs模型文件夹中。In your StoreApp project, add a class file to the Models folder named Product.cs. 将文件的内容替换为以下代码。Replace the contents of the file with the following code.

using System;

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

生成解决方案。Build the solution.

右键单击 "控制器" 文件夹,然后选择 "添加新建基架项"。Right-click the Controllers folder and select Add and New Scaffolded Item. 选择 " WEB API 2 控制器-空"。Select Web API 2 Controller - Empty.

添加新控制器

将控制器名称设置为SimpleProductController,并单击 "添加"。Set the controller name to SimpleProductController, and click Add.

指定控制器

用下面的代码替换现有代码。Replace the existing code with the following code. 为了简化此示例,数据存储在列表中,而不是数据库中。To simplify this example, the data is stored in a list rather than a database. 此类中定义的列表表示生产数据。The list defined in this class represents the production data. 请注意,控制器包含一个构造函数,该构造函数采用产品对象列表作为参数。Notice that the controller includes a constructor that takes as a parameter a list of Product objects. 此构造函数使你能够在单元测试时传递测试数据。This constructor enables you to pass test data when unit testing. 控制器还包括两个异步方法,用于说明单元测试的异步方法。The controller also includes two async methods to illustrate unit testing asynchronous methods. 这些异步方法是通过调用system.threading.tasks.task.fromresult来实现的,以最大程度地减少无关的代码,但通常情况下,方法会包括消耗大量资源的操作。These async methods were implemented by calling Task.FromResult to minimize extraneous code, but normally the methods would include resource-intensive operations.

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接口的实例。The GetProduct method returns an instance of the IHttpActionResult interface. IHttpActionResult 是 Web API 2 中的一项新功能,它简化了单元测试的开发。IHttpActionResult is one of the new features in Web API 2, and it simplifies unit test development. 在 IHttpActionResult 命名空间中找到实现了接口的类。Classes that implement the IHttpActionResult interface are found in the System.Web.Http.Results namespace. 这些类表示来自操作请求的可能响应,它们对应于 HTTP 状态代码。These classes represent possible responses from an action request, and they correspond to HTTP status codes.

生成解决方案。Build the solution.

你现在已准备好设置测试项目。You are now ready to set up the test project.

在测试项目中安装 NuGet 包Install NuGet packages in test project

使用空模板创建应用程序时,单元测试项目(StoreApp)不包含任何已安装的 NuGet 包。When you use the Empty template to create an application, the unit test project (StoreApp.Tests) does not include any installed NuGet packages. 其他模板(如 Web API 模板)在单元测试项目中包含一些 NuGet 包。Other templates, such as the Web API template, include some NuGet packages in the unit test project. 对于本教程,必须将 Microsoft ASP.NET Web API 2 核心包添加到测试项目中。For this tutorial, you must include the Microsoft ASP.NET Web API 2 Core package to the test project.

右键单击 "StoreApp" 项目,然后选择 "管理 NuGet 包"。Right-click the StoreApp.Tests project and select Manage NuGet Packages. 您必须选择 StoreApp 项目,才能将包添加到该项目中。You must select the StoreApp.Tests project to add the packages to that project.

管理包

查找并安装 Microsoft ASP.NET Web API 2 核心包。Find and install Microsoft ASP.NET Web API 2 Core package.

安装 web api 核心包

关闭 "管理 NuGet 包" 窗口。Close the Manage NuGet Packages window.

创建测试Create tests

默认情况下,你的测试项目包含一个名为 UnitTest1.cs 的空测试文件。By default, your test project includes an empty test file named UnitTest1.cs. 此文件显示用于创建测试方法的属性。This file shows the attributes you use to create test methods. 对于单元测试,可以使用此文件,也可以创建自己的文件。For your unit tests, you can either use this file or create your own file.

UnitTest1

在本教程中,您将创建自己的测试类。For this tutorial, you will create your own test class. 你可以删除 UnitTest1.cs 文件。You can delete the UnitTest1.cs file. 添加一个名为TestSimpleProductController.cs的类,并将代码替换为以下代码。Add a class named TestSimpleProductController.cs, and replace the code with the following code.

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

运行测试Run tests

你现在已准备好运行测试。You are now ready to run the tests. 将测试标记为TestMethod属性的所有方法。All of the method that are marked with the TestMethod attribute will be tested. 从 "测试" 菜单项中,运行测试。From the Test menu item, run the tests.

运行测试

打开 "测试资源管理器" 窗口,并注意测试的结果。Open the Test Explorer window, and notice the results of the tests.

测试结果

摘要Summary

您已完成本教程的学习。You have completed this tutorial. 本教程中的数据特意进行了简化,以专注于单元测试情况。The data in this tutorial was intentionally simplified to focus on unit testing conditions. 有关更高级数据方案的单元测试,请参阅单元测试 ASP.NET Web API 2 时的模拟实体框架For unit testing more advanced data scenarios, see Mocking Entity Framework when Unit Testing ASP.NET Web API 2.