Compartilhar via


Simulando o Entity Framework quando o Teste de Unidade ASP.NET Web API 2

por Tom FitzMacken

Baixar Projeto Concluído

Estas diretrizes e aplicativos demonstram como criar testes de unidade para seu aplicativo da API Web 2 que usa o Entity Framework. Ele mostra como modificar o controlador scaffolded para habilitar a passagem de um objeto de contexto para teste e como criar objetos de teste que funcionam com o Entity Framework.

Para obter uma introdução ao teste de unidade com ASP.NET Web API, consulte Teste de unidade com ASP.NET Web API 2.

Este tutorial pressupõe que você esteja familiarizado com os conceitos básicos de ASP.NET Web API. Para obter um tutorial introdutório, consulte Introdução com ASP.NET Web API 2.

Versões de software usadas no tutorial

Neste tópico

Este tópico contém as seguintes seções:

Se você já tiver concluído as etapas em Teste de Unidade com ASP.NET Web API 2, poderá pular para a seção Adicionar o controlador.

Pré-requisitos

Visual Studio 2017 Community, Professional ou Enterprise Edition

Código de download

Baixe o projeto concluído. O projeto para download inclui o código de teste de unidade para este tópico e para o tópico Teste de Unidade ASP.NET Web API 2.

Criar aplicativo com o projeto de teste de unidade

Você pode criar um projeto de teste de unidade ao criar seu aplicativo ou adicionar um projeto de teste de unidade a um aplicativo existente. Este tutorial mostra a criação de um projeto de teste de unidade ao criar o aplicativo.

Crie um novo aplicativo Web ASP.NET chamado StoreApp.

Nas janelas Novo projeto ASP.NET, selecione o modelo Vazio e adicione pastas e referências principais para a API Web. Selecione a opção Adicionar testes de unidade . O projeto de teste de unidade é automaticamente denominado StoreApp.Tests. Você pode manter esse nome.

criar projeto de teste de unidade

Depois de criar o aplicativo, você verá que ele contém dois projetos : StoreApp e StoreApp.Tests.

Criar a classe de modelo

No projeto StoreApp, adicione um arquivo de classe à pasta Models chamada Product.cs. Substitua o conteúdo do arquivo pelo código a seguir.

using System;

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

Compile a solução.

Adicionar controlador

Clique com o botão direito do mouse na pasta Controladores e selecione Adicionar e Novo Item Com Scaffolded. Selecione Controlador da API Web 2 com ações, usando o Entity Framework.

adicionar novo controlador

Defina os seguintes valores:

  • Nome do controlador: ProductController
  • Classe de modelo: Product
  • Classe de contexto de dados: [Selecione o botão Novo contexto de dados que preenche os valores vistos abaixo]

especificar controlador

Clique em Adicionar para criar o controlador com código gerado automaticamente. O código inclui métodos para criar, recuperar, atualizar e excluir instâncias da classe Product. O código a seguir mostra o método para adicionar um Produto. Observe que o método retorna uma instância de IHttpActionResult.

// POST api/Product
[ResponseType(typeof(Product))]
public IHttpActionResult PostProduct(Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Products.Add(product);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
}

IHttpActionResult é um dos novos recursos na API Web 2 e simplifica o desenvolvimento de teste de unidade.

Na próxima seção, você personalizará o código gerado para facilitar a passagem de objetos de teste para o controlador.

Adicionar injeção de dependência

Atualmente, a classe ProductController é embutida em código para usar uma instância da classe StoreAppContext. Você usará um padrão chamado injeção de dependência para modificar seu aplicativo e remover essa dependência embutida em código. Ao quebrar essa dependência, você pode passar um objeto fictício ao testar.

Clique com o botão direito do mouse na pasta Modelos e adicione uma nova interface chamada IStoreAppContext.

Substitua o código pelo código seguinte.

using System;
using System.Data.Entity;

namespace StoreApp.Models
{
    public interface IStoreAppContext : IDisposable
    {
        DbSet<Product> Products { get; }
        int SaveChanges();
        void MarkAsModified(Product item);    
    }
}

Abra o arquivo StoreAppContext.cs e faça as seguintes alterações realçadas. As alterações importantes a serem observadas são:

  • A classe StoreAppContext implementa a interface IStoreAppContext
  • O método MarkAsModified é implementado
using System;
using System.Data.Entity;

namespace StoreApp.Models
{
    public class StoreAppContext : DbContext, IStoreAppContext
    {
        public StoreAppContext() : base("name=StoreAppContext")
        {
        }

        public DbSet<Product> Products { get; set; }
    
        public void MarkAsModified(Product item)
        {
            Entry(item).State = EntityState.Modified;
        }
    }
}

Abra o arquivo ProductController.cs. Altere o código existente para corresponder ao código realçado. Essas alterações interrompem a dependência de StoreAppContext e permitem que outras classes passem um objeto diferente para a classe de contexto. Essa alteração permitirá que você passe em um contexto de teste durante testes de unidade.

public class ProductController : ApiController
{
    // modify the type of the db field
    private IStoreAppContext db = new StoreAppContext();

    // add these constructors
    public ProductController() { }

    public ProductController(IStoreAppContext context)
    {
        db = context;
    }
    // rest of class not shown
}

Há mais uma alteração que você deve fazer no ProductController. No método PutProduct , substitua a linha que define o estado da entidade a ser modificado por uma chamada para o método MarkAsModified.

// PUT api/Product/5
public IHttpActionResult PutProduct(int id, Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (id != product.Id)
    {
        return BadRequest();
    }

    //db.Entry(product).State = EntityState.Modified;
    db.MarkAsModified(product);
    
    // rest of method not shown
}

Compile a solução.

Agora você está pronto para configurar o projeto de teste.

Instalar pacotes NuGet no projeto de teste

Quando você usa o modelo Vazio para criar um aplicativo, o projeto de teste de unidade (StoreApp.Tests) não inclui nenhum pacote NuGet instalado. Outros modelos, como o modelo de API Web, incluem alguns pacotes NuGet no projeto de teste de unidade. Para este tutorial, você deve incluir o pacote do Entity Framework e o pacote Do Microsoft ASP.NET Web API 2 Core para o projeto de teste.

Clique com o botão direito do mouse no projeto StoreApp.Tests e selecione Gerenciar Pacotes NuGet. Você deve selecionar o projeto StoreApp.Tests para adicionar os pacotes a esse projeto.

gerenciar pacotes

Nos pacotes Online, localize e instale o pacote EntityFramework (versão 6.0 ou posterior). Se parecer que o pacote EntityFramework já está instalado, talvez você tenha selecionado o projeto StoreApp em vez do projeto StoreApp.Tests.

adicionar o Entity Framework

Localize e instale o pacote Do Microsoft ASP.NET Web API 2 Core.

instalar o pacote de núcleo da API Web

Feche a janela Gerenciar Pacotes NuGet.

Criar contexto de teste

Adicione uma classe chamada TestDbSet ao projeto de teste. Essa classe serve como a classe base para o conjunto de dados de teste. Substitua o código pelo código seguinte.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;

namespace StoreApp.Tests
{
    public class TestDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T>
        where T : class
    {
        ObservableCollection<T> _data;
        IQueryable _query;

        public TestDbSet()
        {
            _data = new ObservableCollection<T>();
            _query = _data.AsQueryable();
        }

        public override T Add(T item)
        {
            _data.Add(item);
            return item;
        }

        public override T Remove(T item)
        {
            _data.Remove(item);
            return item;
        }

        public override T Attach(T item)
        {
            _data.Add(item);
            return item;
        }

        public override T Create()
        {
            return Activator.CreateInstance<T>();
        }

        public override TDerivedEntity Create<TDerivedEntity>()
        {
            return Activator.CreateInstance<TDerivedEntity>();
        }

        public override ObservableCollection<T> Local
        {
            get { return new ObservableCollection<T>(_data); }
        }

        Type IQueryable.ElementType
        {
            get { return _query.ElementType; }
        }

        System.Linq.Expressions.Expression IQueryable.Expression
        {
            get { return _query.Expression; }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return _query.Provider; }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return _data.GetEnumerator();
        }
    }
}

Adicione uma classe chamada TestProductDbSet ao projeto de teste que contém o código a seguir.

using System;
using System.Linq;
using StoreApp.Models;

namespace StoreApp.Tests
{
    class TestProductDbSet : TestDbSet<Product>
    {
        public override Product Find(params object[] keyValues)
        {
            return this.SingleOrDefault(product => product.Id == (int)keyValues.Single());
        }
    }
}

Adicione uma classe chamada TestStoreAppContext e substitua o código existente pelo código a seguir.

using System;
using System.Data.Entity;
using StoreApp.Models;

namespace StoreApp.Tests
{
    public class TestStoreAppContext : IStoreAppContext 
    {
        public TestStoreAppContext()
        {
            this.Products = new TestProductDbSet();
        }

        public DbSet<Product> Products { get; set; }

        public int SaveChanges()
        {
            return 0;
        }

        public void MarkAsModified(Product item) { }
        public void Dispose() { }
    }
}

Criar testes

Por padrão, seu projeto de teste inclui um arquivo de teste vazio chamado UnitTest1.cs. Esse arquivo mostra os atributos que você usa para criar métodos de teste. Para este tutorial, você pode excluir esse arquivo porque adicionará uma nova classe de teste.

Adicione uma classe chamada TestProductController ao projeto de teste. Substitua o código pelo código seguinte.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.Http.Results;
using System.Net;
using StoreApp.Models;
using StoreApp.Controllers;

namespace StoreApp.Tests
{
    [TestClass]
    public class TestProductController
    {
        [TestMethod]
        public void PostProduct_ShouldReturnSameProduct()
        {
            var controller = new ProductController(new TestStoreAppContext());

            var item = GetDemoProduct();

            var result =
                controller.PostProduct(item) as CreatedAtRouteNegotiatedContentResult<Product>;

            Assert.IsNotNull(result);
            Assert.AreEqual(result.RouteName, "DefaultApi");
            Assert.AreEqual(result.RouteValues["id"], result.Content.Id);
            Assert.AreEqual(result.Content.Name, item.Name);
        }

        [TestMethod]
        public void PutProduct_ShouldReturnStatusCode()
        {
            var controller = new ProductController(new TestStoreAppContext());

            var item = GetDemoProduct();

            var result = controller.PutProduct(item.Id, item) as StatusCodeResult;
            Assert.IsNotNull(result);
            Assert.IsInstanceOfType(result, typeof(StatusCodeResult));
            Assert.AreEqual(HttpStatusCode.NoContent, result.StatusCode);
        }

        [TestMethod]
        public void PutProduct_ShouldFail_WhenDifferentID()
        {
            var controller = new ProductController(new TestStoreAppContext());

            var badresult = controller.PutProduct(999, GetDemoProduct());
            Assert.IsInstanceOfType(badresult, typeof(BadRequestResult));
        }

        [TestMethod]
        public void GetProduct_ShouldReturnProductWithSameID()
        {
            var context = new TestStoreAppContext();
            context.Products.Add(GetDemoProduct());

            var controller = new ProductController(context);
            var result = controller.GetProduct(3) as OkNegotiatedContentResult<Product>;

            Assert.IsNotNull(result);
            Assert.AreEqual(3, result.Content.Id);
        }

        [TestMethod]
        public void GetProducts_ShouldReturnAllProducts()
        {
            var context = new TestStoreAppContext();
            context.Products.Add(new Product { Id = 1, Name = "Demo1", Price = 20 });
            context.Products.Add(new Product { Id = 2, Name = "Demo2", Price = 30 });
            context.Products.Add(new Product { Id = 3, Name = "Demo3", Price = 40 });

            var controller = new ProductController(context);
            var result = controller.GetProducts() as TestProductDbSet;

            Assert.IsNotNull(result);
            Assert.AreEqual(3, result.Local.Count);
        }

        [TestMethod]
        public void DeleteProduct_ShouldReturnOK()
        {
            var context = new TestStoreAppContext();
            var item = GetDemoProduct();
            context.Products.Add(item);

            var controller = new ProductController(context);
            var result = controller.DeleteProduct(3) as OkNegotiatedContentResult<Product>;

            Assert.IsNotNull(result);
            Assert.AreEqual(item.Id, result.Content.Id);
        }

        Product GetDemoProduct()
        {
            return new Product() { Id = 3, Name = "Demo name", Price = 5 };
        }
    }
}

Executar testes

Agora você está pronto para executar os testes. Todo o método marcado com o atributo TestMethod será testado. No item de menu Testar , execute os testes.

executar testes

Abra a janela Testar Explorer e observe os resultados dos testes.

resultados de testes