• RESTful日#8:使用NUnit和Moq框架进行WebAPI的单元测试和集成测试(第二部分)


    表的内容 目录介绍路线图设置解决方案测试WebAPI 步骤1:测试项目步骤2:安装NUnit包步骤3:安装Moq框架步骤4:安装实体框架步骤5:Newtonsoft。json步骤6:引用productcontroller测试 测试设置 声明变量编写测试Fixture设置编写测试Fixture拆卸编写测试拆卸模拟存储库 getproductbyidtest () 测试WebAPI中的异常 GetProductByWrongIdTest () getproductbyinvalididtest () createproducttest () updateproducttest () deleteinvalidproducttest () deleteproducidtest () 集成测试单元测试和集成测试的区别 结论 介绍 在我的上一篇文章中,我解释了如何为业务服务层编写单元测试。在本文中,我们将学习如何为WebAPI控制器编写单元测试,即REST的实际端点。我将使用NUnit和Moq框架为控制器方法编写测试用例。我已经解释了如何安装NUnit和配置单元测试。我的上一篇文章还解释了在编写单元测试中使用的NUnit属性。在阅读本文之前,请阅读本系列的最后一篇文章。 路线图 下面是我逐步学习WebAPI的路线图: RESTful日#1:企业级应用程序架构,使用实体框架、通用存储库模式和工作单元的Web api。RESTful日#2:使用Unity容器和引导程序在Web api中使用依赖注入实现控制反转。RESTful日#3:使用Unity容器和可管理扩展框架(MEF)在Asp.net Web api中使用控制反转和依赖注入来解决依赖关系的依赖关系。RESTful日#4:使用MVC 4 Web api中的属性路由自定义URL重写/路由。RESTful日#5:使用操作过滤器的Web api中基于基本身份验证和令牌的自定义授权。RESTful日#6:使用操作过滤器、异常过滤器和NLog在Web api中进行请求日志记录和异常处理/日志记录。RESTful日#7:使用NUnit和Moq框架在WebAPI中进行单元测试和集成测试(第一部分)。使用NUnit和Moq框架在WebAPI中进行单元测试和集成测试(第二部分)。净Web api。RESTful日#10:创建自托管的ASP。NET WebAPI与CRUD操作在Visual Studio 2010 我有意使用Visual Studio 2010和。net Framework 4.0,因为在。net Framework 4.0中有一些很难找到的实现,但是我将通过演示如何实现来简化它。 设置解决方案 当你从我上一篇文章中取出代码库并在Visual Studio中打开它时,你会看到项目结构如下图所示: 该解决方案包含WebAPI应用程序和相关项目。有两个新添加的名为BusinessServices的项目。测试和TestHelper。我们将使用TestHelper项目及其类来编写WebAPI单元测试,就像我们使用它编写业务服务单元测试一样。 测试之前 步骤1:测试项目 在现有的Visual Studio中添加一个简单的类库,并将其命名为apicontroler . tests。打开Tools->Library包管理器->包管理器控制台以打开包管理器控制台窗口。在继续之前,我们需要安装与业务服务相同的包。 步骤2:安装NUnit包 在包管理器控制台中,选择ApiController。作为默认项目进行测试,并编写命令“Install-Package NUnit -Version 2.6.4”。当您运行该命令时,它说NUnit已经安装。这是因为我们为BusinessServices安装了这个包。测试项目,但对新项目(在我们的例子中是apicontroler .Tests)再次这样做将不会再次安装它,而是添加一个对NUnit框架库的引用,并在包中标记一个条目。配置的ApiController。测试项目。 安装成功后,您可以在项目引用中看到DLL引用,即nunit.framework, 步骤3:安装Moq框架 按照步骤2中解释的类似方法在同一个项目上安装框架。写命令“安装包Moq。” 步骤4:安装实体框架 安装包EntityFramework版本5.0.0 第五步:Newtonsoft.Json Json。NET是一个流行的。NET高性能JSON框架。我们将使用它来序列化/反序列化请求和响应。 安装包Newtonsoft。Json - version 4.5.11 我们的包。配置,即自动添加到项目看起来像: 隐藏,复制Code

    <?xmlversion="1.0"encoding="utf-8"?>
    <packages>
      <packageid="EntityFramework"version="5.0.0"targetFramework="net40"/>
      <packageid="Moq"version="4.2.1510.2205"targetFramework="net40"/>
      <packageid="Newtonsoft.Json"version="4.5.11"targetFramework="net40"/>
      <packageid="NUnit"version="2.6.4"targetFramework="net40"/>
    </packages>

    第六步:引用 向该项目添加BusinessEntities、BusinessServices、DataModel、TestsHelper、WebApi项目的引用。 ProductController测试 我们将从sett开始设置项目并设置测试的先决条件,然后逐渐转移到实际测试。 测试设置 在ApiController中添加一个名为ProductControllerTest.cs的新类。测试项目。 声明变量 定义我们将在类中用于编写测试的私有变量。 隐藏,复制Code

    #region Variables
    
            private IProductServices _productService;
            private ITokenServices _tokenService;
            private IUnitOfWork _unitOfWork;
            private List<Product> _products;
            private List<Token> _tokens;
            private GenericRepository<Product> _productRepository;
            private GenericRepository<Token> _tokenRepository;
            private WebApiDbEntities _dbEntities;
            private HttpClient _client;
    
            private HttpResponseMessage _response;
            private string _token;
            private const string ServiceBaseURL = "http://localhost:50875/";
    
            #endregion

    变量声明是自解释的,_productService将模拟ProductServices, _tokenService将模拟TokenServices, _unitOfWork UnitOfWork类,商品将虚拟产品DataInitializer TestHelper项目类,__tokens将虚拟令牌从DataInitializer类TestHelper项目_productRepository, tokenRepository _dbEntities持有模拟产品库,分别来自DataModel项目的Token Repository和WebAPIDbEntities。 因为WebAPI应该以HttpResponse格式返回响应,所以_response被声明来存储返回的响应,我们可以对其进行断言。_token保存成功身份验证后的标记值。在本文中可能不需要_client和ServiceBaseURL,但是您可以使用它们来编写集成测试,从而有意地使用实际的API URL并在实际的数据库上进行测试。 编写测试设备设置 在顶部使用[TestFixtureSetUp]属性编写测试fixture设置方法,该方法在执行测试时只运行一次。 隐藏,复制Code

    [TestFixtureSetUp]
    public void Setup()
    {
        _products = SetUpProducts();
        _tokens = SetUpTokens();
        _dbEntities = new Mock<WebApiDbEntities>().Object;
        _tokenRepository = SetUpTokenRepository();
        _productRepository = SetUpProductRepository();
        var unitOfWork = new Mock<IUnitOfWork>();
        unitOfWork.SetupGet(s => s.ProductRepository).Returns(_productRepository);
        unitOfWork.SetupGet(s => s.TokenRepository).Returns(_tokenRepository);
        _unitOfWork = unitOfWork.Object;
        _productService = new ProductServices(_unitOfWork);
        _tokenService = new TokenServices(_unitOfWork);
        _client = new HttpClient { BaseAddress = new Uri(ServiceBaseURL) };
        var tokenEntity = _tokenService.GenerateToken(1);
        _token = tokenEntity.AuthToken;
        _client.DefaultRequestHeaders.Add("Token", _token);
    }

    此方法的目的类似于我们为业务服务编写的方法。这里,SetupProducts()将获取虚拟产品的列表,而SetupTokens()将获得虚拟令牌的列表。我们还尝试为令牌和产品存储库设置mock,并在模拟单元工作之后针对已模拟的令牌和产品存储库设置mock。_productService和_tokenService分别是ProductService和TokenService的实例,都是用模拟的工作单元初始化的。 下面是我想多解释的代码行: 隐藏,复制Code

    var tokenEntity = _tokenService.GenerateToken(1);
    _token = tokenEntity.AuthToken;
    _client.DefaultRequestHeaders.Add("Token", _token);
    

    在上面的代码中,我们正在初始化_client,即在请求头中使用令牌值的HttpClient。我们这样做是因为,如果你还记得我们在Product Controller中实现的安全(认证和授权),它说除非授权,否则任何请求都不会被接受,即在其头中包含一个认证令牌。因此,在这里,我们通过TokenService的GenerateToken()方法生成令牌,传递默认使用的id为“1”,并使用该令牌进行授权。我们只在执行集成测试时需要它,因为对于单元测试,我们将直接从单元测试方法中调用控制器方法,但是对于实际的集成测试,在调用API端点之前,您必须模拟所有的前置条件。 SetUpProducts (): 隐藏,复制Code

    private static List<Product> SetUpProducts()
          {
              var prodId = new int();
              var products = DataInitializer.GetAllProducts();
              foreach (Product prod in products)
                  prod.ProductId = ++prodId;
              return products;
          }
    

    SetUpTokens (): 隐藏,复制Code

    private static List<Token> SetUpTokens()
          {
              var tokId = new int();
              var tokens = DataInitializer.GetAllTokens();
              foreach (Token tok in tokens)
                  tok.TokenId = ++tokId;
              return tokens;
          }
    

    编写测试夹具拆卸 与[TestFixtureTearDown]不同,tear down用于对对象进行分配或释放。 下面是用于拆卸的代码。 隐藏,复制Code

    [TestFixtureTearDown]
    public void DisposeAllObjects()
    {
        _tokenService = null;
        _productService = null;
        _unitOfWork = null;
        _tokenRepository = null;
        _productRepository = null;
        _tokens = null;
        _products = null;
        if (_response != null)
            _response.Dispose();
        if (_client != null)
            _client.Dispose();
    }
    

    写测试设置 在这种情况下,只有在编写集成测试时才需要安装程序。所以你可以选择省略这个。 隐藏,复制Code

    [SetUp]
     public void ReInitializeTest()
     {
         _client = new HttpClient { BaseAddress = new Uri(ServiceBaseURL) };
         _client.DefaultRequestHeaders.Add("Token", _token);
     }
    

    编写测试分解 在每次测试执行完成后调用Test [TearDown]。 隐藏,复制Code

    [TearDown]
    public void DisposeTest()
    {
        if (_response != null)
            _response.Dispose();
        if (_client != null)
            _client.Dispose();
    }
    

    嘲笑库 我创建了一个方法SetUpProductRepository()来模拟产品存储库,并将其分配给ReInitializeTest()方法中的_productrepository,并将其分配给SetUpTokenRepository()来模拟TokenRepository并将其分配给ReInitializeTest()方法中的_tokenRepository。 SetUpProductRepository (): 隐藏,收缩,复制Code

    private GenericRepository<Product> SetUpProductRepository()
    {
        // Initialise repository
        var mockRepo = new Mock<GenericRepository<Product>>(MockBehavior.Default, _dbEntities);
    
        // Setup mocking behavior
        mockRepo.Setup(p => p.GetAll()).Returns(_products);
    
        mockRepo.Setup(p => p.GetByID(It.IsAny<int>()))
        .Returns(new Func<int, Product>(
        id => _products.Find(p => p.ProductId.Equals(id))));
    
        mockRepo.Setup(p => p.Insert((It.IsAny<Product>())))
        .Callback(new Action<Product>(newProduct =>
        {
        dynamic maxProductID = _products.Last().ProductId;
        dynamic nextProductID = maxProductID + 1;
        newProduct.ProductId = nextProductID;
        _products.Add(newProduct);
        }));
    
        mockRepo.Setup(p => p.Update(It.IsAny<Product>()))
        .Callback(new Action<Product>(prod =>
        {
        var oldProduct = _products.Find(a => a.ProductId == prod.ProductId);
        oldProduct = prod;
        }));
    
        mockRepo.Setup(p => p.Delete(It.IsAny<Product>()))
        .Callback(new Action<Product>(prod =>
        {
        var productToRemove =
        _products.Find(a => a.ProductId == prod.ProductId);
    
        if (productToRemove != null)
        _products.Remove(productToRemove);
        }));
    
        // Return mock implementation object
        return mockRepo.Object;
    }
    

    SetUpTokenRepository (): 隐藏,收缩,复制Code

    private GenericRepository<Token> SetUpTokenRepository()
    {
        // Initialise repository
        var mockRepo = new Mock<GenericRepository<Token>>(MockBehavior.Default, _dbEntities);
    
        // Setup mocking behavior
        mockRepo.Setup(p => p.GetAll()).Returns(_tokens);
    
        mockRepo.Setup(p => p.GetByID(It.IsAny<int>()))
        .Returns(new Func<int, Token>(
        id => _tokens.Find(p => p.TokenId.Equals(id))));
    
        mockRepo.Setup(p => p.Insert((It.IsAny<Token>())))
        .Callback(new Action<Token>(newToken =>
        {
        dynamic maxTokenID = _tokens.Last().TokenId;
        dynamic nextTokenID = maxTokenID + 1;
        newToken.TokenId = nextTokenID;
        _tokens.Add(newToken);
        }));
    
        mockRepo.Setup(p => p.Update(It.IsAny<Token>()))
        .Callback(new Action<Token>(token =>
        {
        var oldToken = _tokens.Find(a => a.TokenId == token.TokenId);
        oldToken = token;
        }));
    
        mockRepo.Setup(p => p.Delete(It.IsAny<Token>()))
        .Callback(new Action<Token>(prod =>
        {
        var tokenToRemove =
        _tokens.Find(a => a.TokenId == prod.TokenId);
    
        if (tokenToRemove != null)
        _tokens.Remove(tokenToRemove);
        }));
    
        // Return mock implementation object
        return mockRepo.Object;
    }

    单元测试 现在已经设置好了,我们可以为ProductController编写单元测试了。我们将编写测试来执行所有CRUD操作和作为ProductController一部分的所有操作退出点。 1. GetAllProductsTest () 我们的BusinessServices项目中的ProductService包含一个名为GetAllProducts()的方法,下面是实现 隐藏,复制Code

    [Test]
    public void GetAllProductsTest()
    {
        var productController = new ProductController(_productService)
            {
            Request = new HttpRequestMessage
                {
                Method = HttpMethod.Get,
                RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/all")
                }
            };
        productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
        _response = productController.Get();
    
        var responseResult = JsonConvert.DeserializeObject<List<Product>>(_response.Content.ReadAsStringAsync().Result);
        Assert.AreEqual(_response.StatusCode, HttpStatusCode.OK);
        Assert.AreEqual(responseResult.Any(), true);
        var comparer = new ProductComparer();
        CollectionAssert.AreEqual(
        responseResult.OrderBy(product => product, comparer),
        _products.OrderBy(product => product, comparer), comparer);
    }

    让我一步一步地解释代码。我们首先创建一个ProductController的实例,并初始化控制器的请求属性,使用新的请求消息声明调用http方法为GET,并使用基本托管服务URL初始化RequestUri,并附加方法的实际端点。在这种情况下,初始化RequestUri是不必要的,但是如果您测试实际的服务端点,它将对您有所帮助。在本例中,我们不是测试实际的端点,而是直接控制器方法。 HttpPropertyKeys。HttpConfigurationKey,新的HttpConfiguration()行添加了控制器实例化所需的HttpConfigurationKey的默认HttpConfiguration。 _response = productController.Get ();line调用控制器的Get()方法,该方法从dummy _products列表中获取所有产品。自从retu该方法的rn类型是一个http响应消息,我们需要解析它以获得从该方法发送的JSON结果。理想情况下,API的所有事务都应该仅以JSON或XML的形式发生。这有助于客户端理解响应及其结果集。我们使用NewtonSoft库将从_response中获得的对象反序列化为一个产品列表。这意味着JSON响应被转换为List<Product>以便更好的可访问性和比较性。用JSON列出对象转换之后,我放置了三个断言来测试结果。 Assert.AreEqual (_response。StatusCode HttpStatusCode.OK);行检查响应的http状态代码,预期的是HttpStatusCode.OK。 第二个断言,即assert . areequal (responserester . any (), true);检查我们是否拿到了清单上的项目。第三个断言是我们测试的实际确认断言,它将实际产品列表中的每个产品与返回的产品列表中的每个产品进行比较。 我们得到了两个列表,我们需要检查列表的比较,我刚刚按了F5,在TestUI上得到了结果, 这表明我们的测试通过了,即预期的结果和返回的结果是相同的。 2. GetProductByIdTest () 如果我们尝试调用product控制器的GetProductById()方法,这个单元测试将验证返回的结果是否正确。 隐藏,复制Code

    [Test]
        public void GetProductByIdTest()
        {
            var productController = new ProductController(_productService)
            {
            Request = new HttpRequestMessage
            {
            Method = HttpMethod.Get,
            RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/productid/2")
            }
            };
            productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
            _response = productController.Get(2);
    
            var responseResult = JsonConvert.DeserializeObject<Product>(_response.Content.ReadAsStringAsync().Result);
            Assert.AreEqual(_response.StatusCode, HttpStatusCode.OK);
            AssertObjects.PropertyValuesAreEquals(responseResult,
            _products.Find(a => a.ProductName.Contains("Mobile")));
    }

    我使用了一个示例产品id“2”来测试该方法。同样,我们在HttpResponse中得到了反序列化的JSON格式的结果。第一个断言比较状态代码,第二个断言使用AssertObject类将返回的产品的属性与实际的“移动”命名产品的属性进行比较,产品列表中的产品id为2。 测试WebAPI中的异常 NUnit甚至为测试异常提供了灵活性。现在,如果我们想单元测试GetProductById()方法的备用出口点,也就是一个异常,那么我们应该怎么做?记住,测试business services方法的备用出口点很容易,因为它返回null。现在在exception的情况下,NUnit提供了一个ExpectedException属性。我们可以定义期望从方法调用返回的异常类型。就像如果我使用错误的id调用相同的方法,预期它应该返回一个异常ErrorCode 1001和一个错误描述,告诉“没有找到该id的产品”。 所以在我们的例子中,预期的异常类型是ApiDataException(从控制器方法获得)。因此,我们可以将异常属性定义为[ExpectedException("WebApi.ErrorHelper.ApiDataException")] 调用id错误的控制器方法。但是还有另一种方法来断言异常。NUnit还为我们提供了通过assert . throws断言异常的灵活性。该语句断言异常并将该异常返回给调用者。一旦我们得到特定的异常,我们可以断言它的ErrorCode和ErrorDescription或任何你想要的属性。 3.GetProductByWrongIdTest () 隐藏,复制Code

    [Test]
    //[ExpectedException("WebApi.ErrorHelper.ApiDataException")]
    public void GetProductByWrongIdTest()
    {
        var productController = new ProductController(_productService)
        {
            Request = new HttpRequestMessage
            {
                Method = HttpMethod.Get,
                RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/productid/10")
            }
        };
        productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
        var ex = Assert.Throws<ApiDataException>(() => productController.Get(10));
        Assert.That(ex.ErrorCode,Is.EqualTo(1001));
        Assert.That(ex.ErrorDescription, Is.EqualTo("No product found for this id."));
    
    }
    

    在上面的代码中,我已经注释掉了异常属性方法,并遵循了替代方法。 我在声明中用错误的id(在我们的产品列表中不存在)调用了方法: 隐藏,复制Code

    var ex = Assert.Throws<ApiDataException>(() => productController.Get(10));

    上面的语句期望ApiDataException并将返回的异常存储在“ex”中。 现在我们可以断言像ErrorCode和ErrorDescription这样的“ex”异常属性和实际期望的结果。 4. GetProductByInvalidIdTest () 同一方法的另一个出口点是,如果对产品的请求是无效的,那么就会抛出异常。让我们为这个场景测试这个方法: 隐藏,复制Code

    [Test]
    // [ExpectedException("WebApi.ErrorHelper.ApiException")]
     public void GetProductByInvalidIdTest()
     {
         var productController = new ProductController(_productService)
         {
             Request = new HttpRequestMessage
             {
                 Method = HttpMethod.Get,
                 RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/productid/-1")
             }
         };
         productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
         var ex = Assert.Throws<ApiException>(() => productController.Get(-1));
         Assert.That(ex.ErrorCode, Is.EqualTo((int)HttpStatusCode.BadRequest));
         Assert.That(ex.ErrorDescription, Is.EqualTo("Bad Request..."));
     }
    

    我传递了一个无效的id,即-1给控制器方法,它抛出了一个异常类型为ApiException, ErrorCode等于HttpStatusCode。错误请求和错误描述等于“错误请求……”。 测试结果: 即通过。其他的测试和我解释的非常相似。 5. CreateProductTest () 隐藏,收缩,复制Code

    /// <summary>
    /// Create product test
    /// </summary>
    [Test]
    public void CreateProductTest()
    {
        var productController = new ProductController(_productService)
        {
            Request = new HttpRequestMessage
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/Create")
            }
        };
        productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
        var newProduct = new ProductEntity()
        {
            ProductName = "Android Phone"
        };
    
        var maxProductIDBeforeAdd = _products.Max(a => a.ProductId);
        newProduct.ProductId = maxProductIDBeforeAdd + 1;
        productController.Post(newProduct);
        var addedproduct = new Product() { ProductName = newProduct.ProductName, ProductId = newProduct.ProductId };
        AssertObjects.PropertyValuesAreEquals(addedproduct, _products.Last());
        Assert.That(maxProductIDBeforeAdd + 1, Is.EqualTo(_products.Last().ProductId));
    }
    

    6. updateproduct () 隐藏,复制Code

    /// <summary>
    /// Update product test
    /// </summary>
    [Test]
    public void UpdateProductTest()
    {
        var productController = new ProductController(_productService)
        {
            Request = new HttpRequestMessage
            {
                Method = HttpMethod.Put,
                RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/Modify")
            }
        };
        productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
        var firstProduct = _products.First();
        firstProduct.ProductName = "Laptop updated";
        var updatedProduct = new ProductEntity() { ProductName = firstProduct.ProductName, ProductId = firstProduct.ProductId };
        productController.Put(firstProduct.ProductId, updatedProduct);
        Assert.That(firstProduct.ProductId, Is.EqualTo(1)); // hasn't changed
    }
    

    7. DeleteProductTest () 隐藏,复制Code

    /// <summary>
    /// Delete product test
    /// </summary>
    [Test]
    public void DeleteProductTest()
    {
        var productController = new ProductController(_productService)
        {
            Request = new HttpRequestMessage
            {
                Method = HttpMethod.Put,
                RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/Remove")
            }
        };
        productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
        int maxID = _products.Max(a => a.ProductId); // Before removal
        var lastProduct = _products.Last();
    
        // Remove last Product
        productController.Delete(lastProduct.ProductId);
        Assert.That(maxID, Is.GreaterThan(_products.Max(a => a.ProductId))); // Max id reduced by 1
    }
    

    8. DeleteInvalidProductTest () 隐藏,复制Code

    /// <summary>
    /// Delete product test with invalid id
    /// </summary>
    [Test]
    public void DeleteProductInvalidIdTest()
    {
        var productController = new ProductController(_productService)
        {
            Request = new HttpRequestMessage
            {
                Method = HttpMethod.Put,
                RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/remove")
            }
        };
        productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
        var ex = Assert.Throws<ApiException>(() => productController.Delete(-1));
        Assert.That(ex.ErrorCode, Is.EqualTo((int)HttpStatusCode.BadRequest));
        Assert.That(ex.ErrorDescription, Is.EqualTo("Bad Request..."));
    }
    

    9. DeleteProductWithWrongIdTest () 隐藏,复制Code

    /// <summary>
    /// Delete product test with wrong id
    /// </summary>
    [Test]
    public void DeleteProductWrongIdTest()
    {
        var productController = new ProductController(_productService)
        {
            Request = new HttpRequestMessage
            {
                Method = HttpMethod.Put,
                RequestUri = new Uri(ServiceBaseURL + "v1/Products/Product/remove")
            }
        };
        productController.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
    
        int maxID = _products.Max(a => a.ProductId); // Before removal
    
        var ex = Assert.Throws<ApiDataException>(() => productController.Delete(maxID+1));
        Assert.That(ex.ErrorCode, Is.EqualTo(1002));
        Assert.That(ex.ErrorDescription, Is.EqualTo("Product is already deleted or not exist in system."));
    }
    

    上面提到的所有测试都是不言自明的,它们更像是我们测试BusinessServices的方式。这个想法是为了解释我们如何在WebAPI中编写测试。让我们通过NUnit UI运行所有测试。 通过NUnit UI进行测试 步骤1: 发射NUnit UI。我已经解释了如何在windows机器上安装NUnit。用它的启动图标启动NUnit界面, 步骤2: 打开界面后,单击File ->新项目,并将项目命名为WebAPI。nunit,并将其保存在任何窗口位置。 第三步:现在,点击项目->添加程序集并浏览ApiController。dll(在编译时为单元测试项目创建的库) 步骤4:一旦浏览了程序集,您将看到该测试项目的所有单元测试都在UI中加载,并且在界面上可见。 步骤5:在界面的右侧面板,您将看到一个Run按钮,运行Api控制器的所有测试。只需在左侧的测试树中选择节点ApiController并按下右侧的Run按钮。 运行测试后,您将在右侧看到绿色的进度条,在左侧的所有测试上都有标记。这意味着所有的测试都通过了。如果任何测试失败,您将在测试上得到十字标记和右侧红色的进度条。 但在这里,我们所有的测试都通过了。 集成测试 我将介绍一下什么是集成测试以及如何编写它。集成测试不在内存中运行。对于WebAPI来说,编写集成测试的最佳实践是当WebAPI是自托管的时候。您可以尝试在托管API时编写集成测试,以便获得要测试的服务的实际URL或端点。测试是在实际数据和实际服务上执行的。让我们继续看一个例子。我托管了我的web api,我想测试WebAPI的GetAllProducts()方法。针对特定控制器操作的托管URL是http://localhost:50875/v1/ product/product/allproducts。 现在我知道我不会通过dll来测试我的控制器方法参考但实际上我想测试它的端点,我需要通过身份验证令牌,因为终点是安全,不能授权,直到我添加一个安全令牌请求头。下面是GetAllProducts()的集成测试。 隐藏,复制Code

    [Test]
    public void GetAllProductsIntegrationTest()
    {
        #region To be written inside Setup method specifically for integration tests
            var client = new HttpClient { BaseAddress = new Uri(ServiceBaseURL) };
            client.DefaultRequestHeaders.Add("Authorization", "Basic YWtoaWw6YWtoaWw=");
            MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
            _response = client.PostAsync("login", null).Result;
    
            if (_response != null && _response.Headers != null && _response.Headers.Contains("Token") && _response.Headers.GetValues("Token") != null)
            {
            client.DefaultRequestHeaders.Clear();
            _token = ((string[])(_response.Headers.GetValues("Token")))[0];
            client.DefaultRequestHeaders.Add("Token", _token);
            }
        #endregion
    
        _response = client.GetAsync("v1/Products/Product/allproducts/").Result;
        var responseResult =
        JsonConvert.DeserializeObject<List<ProductEntity>>(_response.Content.ReadAsStringAsync().Result);
        Assert.AreEqual(_response.StatusCode, HttpStatusCode.OK);
        Assert.AreEqual(responseResult.Any(), true);
    }

    我使用了相同的类来编写这个测试,但是您应该始终将单元测试与集成测试隔离开来,因此使用另一个测试项目来编写web api的集成测试。首先,我们需要请求一个令牌并将其添加到客户端请求中, 隐藏,复制Code

    var client = new HttpClient { BaseAddress = new Uri(ServiceBaseURL) };
    client.DefaultRequestHeaders.Add("Authorization", "Basic YWtoaWw6YWtoaWw=");
    MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
    _response = client.PostAsync("login", null).Result;
    
    if (_response != null && _response.Headers != null && _response.Headers.Contains("Token") && _response.Headers.GetValues("Token") != null)
    {
    client.DefaultRequestHeaders.Clear();
    _token = ((string[])(_response.Headers.GetValues("Token")))[0];
    client.DefaultRequestHeaders.Add("Token", _token);
    }

    在上面的代码中,我已经用运行服务的基本URL(即http://localhost:50875)初始化了客户端。在初始化之后,我设置了一个默认的请求头来调用我的身份验证控制器的登录端点来获取有效的令牌。一旦用户使用他的凭证登录,他就会得到一个有效的令牌。要阅读有关安全性的详细信息,请参阅我的一篇关于web api中的安全性的文章。我已经传递了基本身份验证的基本64字符串凭据用户名:Akhil和密码:Akhil。一旦请求得到身份验证,我将在_response中获得一个有效的令牌。我获取并分配给_token变量的头文件,并使用这行代码添加到客户端的默认头文件中, 隐藏,复制Code

    _token = ((string[])(_response.Headers.GetValues("Token")))[0];
    client.DefaultRequestHeaders.Add("Token", _token);

    然后我从同一客户端调用实际的服务URL, 隐藏,复制Code

    _response = client.GetAsync("v1/Products/Product/allproducts/").Result;

    我们得到了成功的结果。跟随屏幕截图。 步骤1:获得令牌 我们得到令牌:4bffc06f-d8b1-4eda-b4e6-df9568dd53b1。因为这是一个实时测试。这个令牌应该保存在数据库中。让我们检查。 步骤2:检查数据库 我们在database中得到了相同的令牌。这证明我们是在真实的现场网址测试。 步骤3:检查ResponseResult 这里我们得到了6个产品的响应结果,其中第一个产品id为1,产品名称为“Laptop”。查看数据库查看完整的产品列表: 我们得到了相同的数据。这证明我们的测试是成功的。 同样,您也可以编写更多的集成测试。 单元测试和集成测试的区别 我不会写太多,但是我想通过这个参考链接分享我在这方面的一篇好文章 单元测试集成测试单元测试是一种测试类型,用来检查一小段代码是否完成了它应该做的事情。集成测试是一种测试类型,用于检查模块的不同部分是否在一起工作。单元测试检查应用程序的单个组件。在集成测试中考虑集成模块的行为。单元测试的范围很窄,它涵盖了被测试的单元或小段代码。因此,在编写单元测试时,只针对单个类使用较短的代码。集成测试的范围很广,它涵盖了被测试的整个应用程序,需要花费更多的精力来组合在一起。单元测试不应该依赖于被测试单元之外的代码。集成测试依赖于其他外部系统,如数据库、为它们分配的硬件等。这是第一种测试,在软件测试生命周期中进行,通常由开发人员执行。这种类型的测试在单元测试之后和系统测试之前进行,并由测试团队执行。单元测试没有进一步细分为不同的类型。集成测试我集成又分为以下不同类型:自顶向下集成、自底向上集成等等。单元测试从模块规范开始。集成测试从接口规范开始。代码的详细可见性是通过单元测试实现的。集成结构的可见性正在进行集成测试。单元测试主要关注于测试单个单元的功能,而不是发现不同模块相互交互时出现的问题。进行集成测试,以发现不同模块在相互交互以构建整个系统时所出现的问题。单元测试的目标是分别测试每个单元,并确保每个单元按预期工作。集成测试的目标是将组合的模块测试在一起,并确保每个组合的模块都按预期工作。单元测试位于白盒测试类型之下。集成测试属于黑盒测试和白盒测试。 参考:http://www.softwaretestingclass.com/what-is-difference-between-unit-testing-and-integration-testing/ 结论 在本文中,我们学习了如何为Web API控制器编写单元测试,主要是关于基本的凝块操作。其目的是了解单元测试是如何编写和执行的。你可以添加自己的风格,这对你的实时项目有帮助。我们还学习了如何为WebAPI端点编写集成测试。我希望这对你们有用。您可以从GitHub下载本文的完整源代码和包。编码快乐:) 其他系列 我的其他系列文章: MVC架构和关注点分离简介:第1部分:面向对象(第1天):多态性和继承(早期绑定/编译时多态性) 本文转载于:http://www.diyabc.com/frontweb/news412.html

  • 相关阅读:
    InfoPath开发误区
    error: 80040111
    EasyUi通过POI 实现导出xls表格功能
    webservice入门
    CXF框架入门(重点)
    Spring整合Shiro
    EasyUi通过OCUpload上传及POI上传 实现导入xls表格功能
    Matlab自定义安装
    一位大学教师对学生的建议:如何做好研究
    What is covariate(协变量)
  • 原文地址:https://www.cnblogs.com/Dincat/p/13447591.html
Copyright © 2020-2023  润新知