Имитация Entity Framework при модульной проверке веб-API ASP.NET 2
; автор — Том ФитцМакен (Tom FitzMacken)
Скачивание завершенного проекта
В этом руководстве и приложении показано, как создавать модульные тесты для приложения веб-API 2, использующего Entity Framework. В ней показано, как изменить шаблонный контроллер, чтобы разрешить передачу объекта контекста для тестирования, а также как создать тестовые объекты, работающие с Entity Framework.
Общие сведения о модульном тестировании с помощью веб-API ASP.NET см. в разделе Модульное тестирование с помощью веб-API ASP.NET 2.
В этом руководстве предполагается, что вы знакомы с основными понятиями веб-API ASP.NET. Вводное руководство см. в разделе начало работы с веб-API ASP.NET 2.
Версии программного обеспечения, используемые в этом руководстве
- Visual Studio 2017
- Веб-API 2
В этом разделе
Этот раздел состоит из следующих подразделов.
- Предварительные требования
- Скачать код
- Создание приложения с проектом модульного теста
- Создание класса модели
- Добавление контроллера
- Добавление внедрения зависимостей
- Установка пакетов NuGet в тестовом проекте
- Создание тестового контекста
- Создание тестов
- Выполнить тесты
Если вы уже выполнили действия, описанные в разделе Модульное тестирование с помощью веб-API ASP.NET 2, можно перейти к разделу Добавление контроллера.
Предварительные требования
Выпуск Visual Studio 2017 Community, Professional или Enterprise
Скачать код
Скачайте готовый проект. Загружаемый проект содержит код модульного теста для этого раздела и для раздела модульного тестирования веб-API ASP.NET 2.
Создание приложения с проектом модульного теста
Вы можете создать проект модульного теста при создании приложения или добавить проект модульного теста в существующее приложение. В этом руководстве показано, как создать проект модульного теста при создании приложения.
Создайте веб-приложение ASP.NET с именем StoreApp.
В окне Создать ASP.NET Проект выберите пустой шаблон и добавьте папки и основные ссылки для веб-API. Выберите параметр Добавить модульные тесты . Проект модульного теста автоматически называется StoreApp.Tests. Это имя можно сохранить.
После создания приложения вы увидите, что оно содержит два проекта : StoreApp и StoreApp.Tests.
Создание класса модели
В проекте StoreApp добавьте файл класса в папку Modelsс именем Product.cs. Замените содержимое файла на код, приведенный ниже.
using System;
namespace StoreApp.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Создайте решение.
Добавление контроллера
Щелкните правой кнопкой мыши папку Контроллеры и выберите Добавить и Создать шаблонный элемент. Выберите Контроллер веб-API 2 с действиями с помощью Entity Framework.
Задайте следующие значения:
- Имя контроллера: ProductController
- Класс модели: Product
- Класс контекста данных: [Выберите кнопку "Новый контекст данных ", которая заполняет значения, приведенные ниже]
Нажмите кнопку Добавить , чтобы создать контроллер с автоматически созданным кодом. Код включает методы для создания, получения, обновления и удаления экземпляров класса Product. В следующем коде показан метод добавления продукта. Обратите внимание, что метод возвращает экземпляр 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 — это одна из новых функций веб-API 2, упрощая разработку модульных тестов.
В следующем разделе вы настроите созданный код, чтобы упростить передачу тестовых объектов в контроллер.
Добавление внедрения зависимостей
В настоящее время класс ProductController жестко запрограммирован для использования экземпляра класса StoreAppContext. Вы будете использовать шаблон внедрения зависимостей, чтобы изменить приложение и удалить эту жестко закодированную зависимость. Нарушая эту зависимость, можно передать макет объекта при тестировании.
Щелкните правой кнопкой мыши папку Models и добавьте новый интерфейс iStoreAppContext.
Замените код следующим кодом.
using System;
using System.Data.Entity;
namespace StoreApp.Models
{
public interface IStoreAppContext : IDisposable
{
DbSet<Product> Products { get; }
int SaveChanges();
void MarkAsModified(Product item);
}
}
Откройте файл StoreAppContext.cs и внесите указанные ниже изменения. Обратите внимание на важные изменения:
- Класс StoreAppContext реализует интерфейс IStoreAppContext
- Реализован метод MarkAsModified
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;
}
}
}
Откройте файл ProductController.cs. Измените существующий код в соответствии с выделенным кодом. Эти изменения нарушают зависимость от StoreAppContext и позволяют другим классам передавать другой объект для класса контекста. Это изменение позволит передать контекст теста во время модульных тестов.
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
}
В ProductController необходимо внести еще одно изменение. В методе PutProduct замените строку, которая задает измененное состояние сущности, вызовом метода 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
}
Создайте решение.
Теперь все готово для настройки тестового проекта.
Установка пакетов NuGet в тестовом проекте
При использовании пустого шаблона для создания приложения проект модульного теста (StoreApp.Tests) не включает установленные пакеты NuGet. Другие шаблоны, например шаблон веб-API, включают некоторые пакеты NuGet в проект модульного теста. В этом руководстве необходимо включить пакет Entity Framework и пакет Microsoft веб-API ASP.NET 2 Core в тестовый проект.
Щелкните правой кнопкой мыши проект StoreApp.Tests и выберите Управление пакетами NuGet. Чтобы добавить пакеты в этот проект, необходимо выбрать проект StoreApp.Tests.
В веб-пакетах найдите и установите пакет EntityFramework (версии 6.0 или более поздней). Если кажется, что пакет EntityFramework уже установлен, возможно, вы выбрали проект StoreApp вместо проекта StoreApp.Tests.
Найдите и установите пакет Microsoft веб-API ASP.NET 2 Core.
Закройте окно Управление пакетами NuGet.
Создание тестового контекста
Добавьте класс с именем TestDbSet в тестовый проект. Этот класс служит базовым классом для тестового набора данных. Замените код следующим кодом.
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();
}
}
}
Добавьте класс с именем TestProductDbSet в тестовый проект, содержащий следующий код.
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());
}
}
}
Добавьте класс с именем TestStoreAppContext и замените существующий код следующим кодом.
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() { }
}
}
Создание тестов
По умолчанию тестовый проект содержит пустой файл теста с именем UnitTest1.cs. В этом файле показаны атрибуты, используемые для создания методов тестирования. В этом руководстве этот файл можно удалить, так как вы добавите новый тестовый класс.
Добавьте класс с именем TestProductController в тестовый проект. Замените код следующим кодом.
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 };
}
}
}
Выполнить тесты
Теперь все готово к выполнению тестов. Все методы, помеченные атрибутом TestMethod , будут протестированы. В пункте меню Тест запустите тесты.
Откройте окно Тест Обозреватель и обратите внимание на результаты тестов.