Pozorowanie platformy Entity Framework podczas testowania jednostkowego ASP.NET internetowego interfejsu API 2
– autor Tom FitzMacken
Pobieranie ukończonego projektu
Te wskazówki i aplikacja przedstawiają sposób tworzenia testów jednostkowych dla aplikacji internetowego interfejsu API 2 korzystającej z programu Entity Framework. Pokazano w nim, jak zmodyfikować kontroler szkieletu, aby umożliwić przekazywanie obiektu kontekstu do testowania oraz tworzenie obiektów testowych, które współpracują z programem Entity Framework.
Aby zapoznać się z wprowadzeniem do testowania jednostkowego za pomocą internetowego interfejsu API ASP.NET, zobacz Unit Testing with ASP.NET Web API 2 (Testowanie jednostkowe przy użyciu interfejsu API sieci Web ASP.NET 2).
W tym samouczku założono, że znasz podstawowe pojęcia dotyczące ASP.NET internetowego interfejsu API. Aby zapoznać się z samouczkiem wprowadzającym, zobacz Wprowadzenie z interfejsem API sieci Web 2 ASP.NET.
Wersje oprogramowania używane w samouczku
- Visual Studio 2017
- Internetowy interfejs API 2
W tym temacie:
Ten temat zawiera następujące sekcje:
- Wymagania wstępne
- Pobieranie kodu
- Tworzenie aplikacji przy użyciu projektu testów jednostkowych
- Tworzenie klasy modelu
- Dodawanie kontrolera
- Dodawanie wstrzykiwania zależności
- Instalowanie pakietów NuGet w projekcie testowym
- Tworzenie kontekstu testowego
- Tworzenie testów
- Uruchamianie testów
Jeśli kroki opisane w artykule Testowanie jednostkowe zostały już wykonane za pomocą ASP.NET internetowego interfejsu API 2, możesz przejść do sekcji Dodawanie kontrolera.
Wymagania wstępne
Visual Studio 2017 Community, Professional lub Enterprise edition
Pobieranie kodu
Pobierz ukończony projekt. Projekt do pobrania zawiera kod testu jednostkowego dla tego tematu oraz temat Unit Testing ASP.NET Web API 2 .
Tworzenie aplikacji przy użyciu projektu testów jednostkowych
Projekt testu jednostkowego można utworzyć podczas tworzenia aplikacji lub dodać projekt testu jednostkowego do istniejącej aplikacji. W tym samouczku przedstawiono tworzenie projektu testu jednostkowego podczas tworzenia aplikacji.
Utwórz nową aplikację internetową ASP.NET o nazwie StoreApp.
W oknach Nowy ASP.NET Project wybierz pusty szablon i dodaj foldery i odwołania podstawowe dla internetowego interfejsu API. Wybierz opcję Dodaj testy jednostkowe . Projekt testu jednostkowego jest automatycznie nazwany StoreApp.Tests. Możesz zachować tę nazwę.
Po utworzeniu aplikacji zobaczysz, że zawiera dwa projekty — StoreApp i StoreApp.Tests.
Tworzenie klasy modelu
W projekcie StoreApp dodaj plik klasy do folderu Models o nazwie Product.cs. Zastąp zawartość pliku następującym kodem.
using System;
namespace StoreApp.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Skompiluj rozwiązanie.
Dodawanie kontrolera
Kliknij prawym przyciskiem myszy folder Kontrolery i wybierz pozycję Dodaj i Nowy element szkieletowy. Wybierz pozycję Kontroler internetowego interfejsu API 2 z akcjami przy użyciu programu Entity Framework.
Ustaw następujące wartości:
- Nazwa kontrolera: ProductController
- Klasa modelu: Product
- Klasa kontekstu danych: [Wybierz przycisk Nowy kontekst danych , który wypełnia wartości widoczne poniżej]
Kliknij przycisk Dodaj , aby utworzyć kontroler z automatycznie wygenerowanym kodem. Kod zawiera metody tworzenia, pobierania, aktualizowania i usuwania wystąpień klasy Product. Poniższy kod przedstawia metodę dodawania produktu. Zwróć uwagę, że metoda zwraca wystąpienie elementu 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 jest jedną z nowych funkcji w internetowym interfejsie API 2 i upraszcza tworzenie testów jednostkowych.
W następnej sekcji dostosujesz wygenerowany kod, aby ułatwić przekazywanie obiektów testowych do kontrolera.
Dodawanie wstrzykiwania zależności
Obecnie klasa ProductController jest zakodowana w kodzie, aby użyć wystąpienia klasy StoreAppContext. Użyjesz wzorca o nazwie iniekcja zależności, aby zmodyfikować aplikację i usunąć ta zakodowana zależność. Przerywając tę zależność, można przekazać w pozorny obiekt podczas testowania.
Kliknij prawym przyciskiem myszy folder Models i dodaj nowy interfejs o nazwie IStoreAppContext.
Zastąp kod następującym kodem.
using System;
using System.Data.Entity;
namespace StoreApp.Models
{
public interface IStoreAppContext : IDisposable
{
DbSet<Product> Products { get; }
int SaveChanges();
void MarkAsModified(Product item);
}
}
Otwórz plik StoreAppContext.cs i wprowadź następujące wyróżnione zmiany. Ważne zmiany, które należy zanotować, to:
- Klasa StoreAppContext implementuje interfejs IStoreAppContext
- Metoda MarkAsModified jest implementowana
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;
}
}
}
Otwórz plik ProductController.cs. Zmień istniejący kod, aby był zgodny z wyróżnionym kodem. Te zmiany przerywają zależność od klasy StoreAppContext i umożliwiają przekazywanie innych klas w innym obiekcie dla klasy kontekstowej. Ta zmiana umożliwi przekazanie kontekstu testowego podczas testów jednostkowych.
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
}
Istnieje jeszcze jedna zmiana, którą należy wprowadzić w elemecie ProductController. W metodzie PutProduct zastąp wiersz, który ustawia stan jednostki na zmodyfikowany za pomocą wywołania metody 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
}
Skompiluj rozwiązanie.
Teraz możesz przystąpić do konfigurowania projektu testowego.
Instalowanie pakietów NuGet w projekcie testowym
Jeśli używasz szablonu Empty do utworzenia aplikacji, projekt testu jednostkowego (StoreApp.Tests) nie zawiera żadnych zainstalowanych pakietów NuGet. Inne szablony, takie jak szablon internetowego interfejsu API, zawierają niektóre pakiety NuGet w projekcie testu jednostkowego. W tym samouczku należy dołączyć pakiet Entity Framework i pakiet Microsoft ASP.NET Web API 2 Core do projektu testowego.
Kliknij prawym przyciskiem myszy projekt StoreApp.Tests i wybierz polecenie Zarządzaj pakietami NuGet. Aby dodać pakiety do tego projektu, musisz wybrać projekt StoreApp.Tests.
Z poziomu pakietów online znajdź i zainstaluj pakiet EntityFramework (wersja 6.0 lub nowsza). Jeśli okaże się, że pakiet EntityFramework jest już zainstalowany, być może wybrano projekt StoreApp zamiast projektu StoreApp.Tests.
Znajdź i zainstaluj pakiet Microsoft ASP.NET Web API 2 Core.
Zamknij okno Zarządzanie pakietami NuGet.
Tworzenie kontekstu testowego
Dodaj klasę o nazwie TestDbSet do projektu testowego. Ta klasa służy jako klasa bazowa dla zestawu danych testowych. Zastąp kod następującym kodem.
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();
}
}
}
Dodaj klasę o nazwie TestProductDbSet do projektu testowego zawierającego następujący kod.
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());
}
}
}
Dodaj klasę o nazwie TestStoreAppContext i zastąp istniejący kod następującym kodem.
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() { }
}
}
Tworzenie testów
Domyślnie projekt testowy zawiera pusty plik testowy o nazwie UnitTest1.cs. Ten plik przedstawia atrybuty używane do tworzenia metod testowych. W tym samouczku możesz usunąć ten plik, ponieważ dodasz nową klasę testową.
Dodaj klasę o nazwie TestProductController do projektu testowego. Zastąp kod poniższym kodem.
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 };
}
}
}
Uruchom testy
Teraz możesz przystąpić do uruchamiania testów. Wszystkie metody oznaczone atrybutem TestMethod zostaną przetestowane. W elemencie menu Test uruchom testy.
Otwórz okno Eksplorator testów i zwróć uwagę na wyniki testów.