Simulieren von Entity Framework beim Komponententest ASP.NET-Web-API 2
von Tom FitzMacken
Abgeschlossenes Projekt herunterladen
In diesem Leitfaden und in dieser Anwendung wird veranschaulicht, wie Sie Komponententests für Ihre Web-API 2-Anwendung erstellen, die Entity Framework verwendet. Es zeigt, wie Sie den gerüsteten Controller ändern, um die Übergabe eines Kontextobjekts für Tests zu ermöglichen, und wie Sie Testobjekte erstellen, die mit Entity Framework funktionieren.
Eine Einführung in Komponententests mit ASP.NET-Web-API finden Sie unter Komponententests mit ASP.NET-Web-API 2.
In diesem Tutorial wird davon ausgegangen, dass Sie mit den grundlegenden Konzepten der ASP.NET-Web-API vertraut sind. Ein einführungstutorial finden Sie unter Erste Schritte mit ASP.NET-Web-API 2.
Im Tutorial verwendete Softwareversionen
- Visual Studio 2017
- Web-API 2
In diesem Thema
Dieses Thema enthält folgende Abschnitte:
- Voraussetzungen
- Code herunterladen
- Erstellen einer Anwendung mit Komponententestprojekt
- Erstellen der Modellklasse
- Hinzufügen des Controllers
- Hinzufügen von Abhängigkeitsinjektion
- Installieren von NuGet-Paketen im Testprojekt
- Erstellen eines Testkontexts
- Tests erstellen
- Ausführen von Tests
Wenn Sie die Schritte unter Komponententests mit ASP.NET-Web-API 2 bereits ausgeführt haben, können Sie mit dem Abschnitt Hinzufügen des Controllers fortfahren.
Voraussetzungen
Visual Studio 2017 Community, Professional oder Enterprise Edition
Code herunterladen
Laden Sie das fertige Projekt herunter. Das herunterladbare Projekt enthält Komponententestcode für dieses Thema und für das Thema Unit Testing ASP.NET-Web-API 2.
Erstellen einer Anwendung mit Komponententestprojekt
Sie können entweder beim Erstellen Ihrer Anwendung ein Komponententestprojekt erstellen oder einer vorhandenen Anwendung ein Komponententestprojekt hinzufügen. In diesem Tutorial wird das Erstellen eines Komponententestprojekts beim Erstellen der Anwendung veranschaulicht.
Erstellen Sie eine neue ASP.NET-Webanwendung namens StoreApp.
Wählen Sie im Fenster Neue ASP.NET Projekt die Vorlage Leere aus, und fügen Sie Ordner und Kernverweise für die Web-API hinzu. Wählen Sie die Option Komponententests hinzufügen aus. Das Komponententestprojekt heißt automatisch StoreApp.Tests. Sie können diesen Namen beibehalten.
Nachdem Sie die Anwendung erstellt haben, sehen Sie, dass sie zwei Projekte enthält: StoreApp und StoreApp.Tests.
Erstellen der Modellklasse
Fügen Sie in Ihrem StoreApp-Projekt dem Ordner Models eine Klassendatei mit dem Namen Product.cs hinzu. Ersetzen Sie den Inhalt der Datei durch den folgenden Code.
using System;
namespace StoreApp.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Erstellen Sie die Projektmappe.
Hinzufügen des Controllers
Klicken Sie mit der rechten Maustaste auf den Ordner Controller, und wählen Sie Hinzufügen und Neues Gerüstelement aus. Wählen Sie Web-API 2-Controller mit Aktionen mithilfe von Entity Framework aus.
Legen Sie die folgenden Werte fest:
- Controllername: ProductController
- Modellklasse: Produkt
- Datenkontextklasse: [Schaltfläche "Neuer Datenkontext " auswählen, die die unten gezeigten Werte ausfüllt]
Klicken Sie auf Hinzufügen , um den Controller mit automatisch generiertem Code zu erstellen. Der Code enthält Methoden zum Erstellen, Abrufen, Aktualisieren und Löschen von Instanzen der Product-Klasse. Der folgende Code zeigt die -Methode zum Hinzufügen eines Produkts. Beachten Sie, dass die -Methode eine instance von IHttpActionResult zurückgibt.
// 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 ist eines der neuen Features in Web-API 2 und vereinfacht die Entwicklung von Komponententests.
Im nächsten Abschnitt passen Sie den generierten Code an, um das Übergeben von Testobjekten an den Controller zu erleichtern.
Hinzufügen von Abhängigkeitsinjektion
Derzeit ist die ProductController-Klasse hartcodiert, um eine instance der StoreAppContext-Klasse zu verwenden. Sie verwenden ein Muster namens Abhängigkeitsinjektion, um Ihre Anwendung zu ändern und diese hartcodierte Abhängigkeit zu entfernen. Wenn Sie diese Abhängigkeit unterbrechen, können Sie beim Testen ein Pseudoobjekt übergeben.
Klicken Sie mit der rechten Maustaste auf den Ordner Models , und fügen Sie eine neue Schnittstelle mit dem Namen IStoreAppContext hinzu.
Ersetzen Sie den Code durch den folgenden Code.
using System;
using System.Data.Entity;
namespace StoreApp.Models
{
public interface IStoreAppContext : IDisposable
{
DbSet<Product> Products { get; }
int SaveChanges();
void MarkAsModified(Product item);
}
}
Öffnen Sie die Datei StoreAppContext.cs, und nehmen Sie die folgenden hervorgehobenen Änderungen vor. Beachten Sie folgende wichtige Änderungen:
- Die StoreAppContext-Klasse implementiert die IStoreAppContext-Schnittstelle.
- Die MarkAsModified-Methode ist implementiert.
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;
}
}
}
Öffnen Sie die Datei ProductController.cs. Ändern Sie den vorhandenen Code so, dass er mit dem hervorgehobenen Code übereinstimmt. Diese Änderungen unterbrechen die Abhängigkeit von StoreAppContext und ermöglichen es anderen Klassen, ein anderes Objekt für die Kontextklasse zu übergeben. Diese Änderung ermöglicht es Ihnen, einen Testkontext während Komponententests zu übergeben.
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
}
Es gibt eine weitere Änderung, die Sie in ProductController vornehmen müssen. Ersetzen Sie in der PutProduct-Methode die Zeile, die den zu ändernden Entitätszustand festlegt, durch einen Aufruf der MarkAsModified-Methode.
// 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
}
Erstellen Sie die Projektmappe.
Jetzt können Sie das Testprojekt einrichten.
Installieren von NuGet-Paketen im Testprojekt
Wenn Sie die Leere Vorlage zum Erstellen einer Anwendung verwenden, enthält das Komponententestprojekt (StoreApp.Tests) keine installierten NuGet-Pakete. Andere Vorlagen, z. B. die Web-API-Vorlage, enthalten einige NuGet-Pakete im Komponententestprojekt. Für dieses Tutorial müssen Sie das Entity Framework-Paket und das Microsoft ASP.NET-Web-API 2 Core-Paket in das Testprojekt einschließen.
Klicken Sie mit der rechten Maustaste auf das Projekt StoreApp.Tests, und wählen Sie NuGet-Pakete verwalten aus. Sie müssen das StoreApp.Tests-Projekt auswählen, um die Pakete zu diesem Projekt hinzuzufügen.
Suchen Und installieren Sie in den Onlinepaketen das EntityFramework-Paket (Version 6.0 oder höher). Wenn das EntityFramework-Paket bereits installiert ist, haben Sie möglicherweise das StoreApp-Projekt anstelle des Projekts StoreApp.Tests ausgewählt.
Suchen und installieren Sie das Microsoft ASP.NET-Web-API 2 Core-Paket.
Schließen Sie das Fenster NuGet-Pakete verwalten.
Erstellen eines Testkontexts
Fügen Sie dem Testprojekt eine Klasse mit dem Namen TestDbSet hinzu. Diese Klasse dient als Basisklasse für Ihr Testdataset. Ersetzen Sie den Code durch den folgenden Code.
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();
}
}
}
Fügen Sie dem Testprojekt eine Klasse mit dem Namen TestProductDbSet hinzu, die den folgenden Code enthält.
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());
}
}
}
Fügen Sie eine Klasse namens TestStoreAppContext hinzu, und ersetzen Sie den vorhandenen Code durch den folgenden Code.
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() { }
}
}
Tests erstellen
Standardmäßig enthält Ihr Testprojekt eine leere Testdatei mit dem Namen UnitTest1.cs. In dieser Datei werden die Attribute angezeigt, die Sie zum Erstellen von Testmethoden verwenden. In diesem Tutorial können Sie diese Datei löschen, da Sie eine neue Testklasse hinzufügen.
Fügen Sie dem Testprojekt eine Klasse mit dem Namen TestProductController hinzu. Ersetzen Sie den Code durch den folgenden Code.
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 };
}
}
}
Ausführen von Tests
Sie können jetzt die Tests ausführen. Alle Methoden, die mit dem TestMethod-Attribut gekennzeichnet sind, werden getestet. Führen Sie im Menüelement Test die Tests aus.
Öffnen Sie das Fenster Test Explorer, und sehen Sie sich die Ergebnisse der Tests an.