Simulando o Entity Framework quando o Teste de Unidade ASP.NET Web API 2
por Tom FitzMacken
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
- Visual Studio 2017
- API Web 2
Neste tópico
Este tópico contém as seguintes seções:
- Pré-requisitos
- Código de download
- Criar aplicativo com o projeto de teste de unidade
- Criar a classe de modelo
- Adicionar controlador
- Adicionar injeção de dependência
- Instalar pacotes NuGet no projeto de teste
- Criar contexto de teste
- Criar testes
- Executar testes
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.
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.
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]
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.
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.
Localize e instale o pacote Do Microsoft ASP.NET Web API 2 Core.
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.
Abra a janela Testar Explorer e observe os resultados dos testes.