Simulação de Entity Framework quando o teste de unidade ASP.NET Web API 2

por Tom FitzMacken

Baixar projeto concluído

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

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

Este tutorial pressupõe que você esteja familiarizado com os conceitos básicos do 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 testes de unidade com ASP.NET Web API 2, poderá pular para a seção Adicionar o controlador.

Prerequisites

Visual Studio 2017 Community, Professional ou Enterprise Edition

Código de download

Baixe o projeto concluído. O projeto que pode ser baixado 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 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 como criar um projeto de teste de unidade ao criar o aplicativo.

Crie um novo aplicativo Web ASP.NET chamado StoreApp.

Nas janelas do novo projeto ASP.NET, selecione o modelo vazio e adicione as pastas e as referências principais para a API da Web. Selecione a opção Adicionar testes de unidade . O projeto de teste de unidade é chamado automaticamente de 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

Em seu projeto StoreApp, adicione um arquivo de classe à pasta modelos 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 Scaffold. Selecione controlador Web API 2 com ações, usando Entity Framework.

Adicionar novo controlador

Defina os seguintes valores:

  • Nome do controlador: ProductController
  • Classe de modelo: produto
  • Classe de contexto de dados: [botão Selecionar novo contexto de dados que preenche os valores exibidos abaixo]

especificar controlador

Clique em Adicionar para criar o controlador com o 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);
}

O IHttpActionResult é um dos novos recursos da API Web 2 e simplifica o desenvolvimento de testes 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 dividir essa dependência, você pode passar um objeto fictício durante o teste.

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 a seguir.

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:

  • 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 quebram a dependência em StoreAppContext e permitem que outras classes passem em um objeto diferente para a classe de contexto. Essa alteração permitirá que você passe um contexto de teste durante os 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 em ProductController. No método PutProduct , substitua a linha que define o estado da entidade como modificado com 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 de Entity Framework e o pacote de 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 do EntityFramework (versão 6,0 ou posterior). Se parecer que o pacote do EntityFramework já está instalado, você pode ter selecionado o projeto StoreApp em vez do projeto StoreApp. Tests.

Adicionar Entity Framework

Localize e instale Microsoft ASP.NET pacote de núcleo da API Web 2.

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 a seguir.

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, o 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 este arquivo porque você adicionará uma nova classe de teste.

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

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. Todos os métodos marcados com o atributo TestMethod serão testados. No item de menu de teste , execute os testes.

executar testes

Abra a janela Gerenciador de testes e observe os resultados dos testes.

resultados de testes