Napodobení Entity Frameworku při testování jednotek ASP.NET webovém rozhraní API 2

, autor: Tom FitzMacken

Stažení dokončeného projektu

Tyto doprovodné materiály a aplikace ukazují, jak vytvořit testy jednotek pro aplikaci webového rozhraní API 2, která používá Entity Framework. Ukazuje, jak upravit vygenerovaný kontroler tak, aby umožňoval předávání kontextového objektu pro testování, a jak vytvořit testovací objekty, které pracují s Entity Frameworkem.

Úvod do testování částí pomocí webového rozhraní API ASP.NET najdete v tématu Testování jednotek pomocí webového rozhraní API ASP.NET 2.

Tento kurz předpokládá, že znáte základní koncepty ASP.NET webového rozhraní API. Úvodní kurz najdete v tématu Začínáme s webovým rozhraním API ASP.NET 2.

Verze softwaru použité v kurzu

V tomto tématu

Toto téma obsahuje následující oddíly:

Pokud jste už dokončili kroky v části Testování jednotek pomocí webového rozhraní API ASP.NET 2, můžete přeskočit k části Přidání kontroleru.

Požadavky

Edice Visual Studio 2017 Community, Professional nebo Enterprise

Stažení kódu

Stáhněte dokončený projekt. Projekt ke stažení obsahuje kód testu jednotek pro toto téma a pro téma Testování jednotek ASP.NET webovém rozhraní API 2 .

Vytvoření aplikace s projektem testování jednotek

Projekt testování jednotek můžete vytvořit buď při vytváření aplikace, nebo přidat projekt testování jednotek do existující aplikace. Tento kurz ukazuje vytvoření projektu testování jednotek při vytváření aplikace.

Vytvořte novou ASP.NET webovou aplikaci s názvem StoreApp.

V oknech Nový projekt ASP.NET vyberte prázdnou šablonu a přidejte složky a základní odkazy pro webové rozhraní API. Vyberte možnost Přidat testy jednotek . Projekt testování jednotek má automaticky název StoreApp.Tests. Tento název si můžete ponechat.

vytvoření projektu testování jednotek

Po vytvoření aplikace uvidíte, že obsahuje dva projekty – StoreApp a StoreApp.Tests.

Vytvoření třídy modelu

V projektu StoreApp přidejte soubor třídy do složky Models s názvem Product.cs. Obsah souboru nahraďte následujícím kódem.

using System;

namespace StoreApp.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

Sestavte řešení.

Přidání kontroleru

Klikněte pravým tlačítkem na složku Kontrolery a vyberte Přidat a Nová vygenerovaná položka. Vyberte Kontroler webového rozhraní API 2 s akcemi pomocí Entity Frameworku.

přidání nového kontroleru

Nastavte následující hodnoty:

  • Název kontroleru: ProductController
  • Třída modelu: Product
  • Třída kontextu dat: [Vyberte tlačítko Nový kontext dat , které vyplní hodnoty zobrazené níže]

určení kontroleru

Kliknutím na Přidat vytvořte kontroler s automaticky vygenerovaným kódem. Kód obsahuje metody pro vytváření, načítání, aktualizaci a odstraňování instancí třídy Product. Následující kód ukazuje metodu pro přidání Produktu. Všimněte si, že metoda vrátí instanci 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 je jednou z nových funkcí webového rozhraní API 2 a zjednodušuje vývoj testů jednotek.

V další části přizpůsobíte vygenerovaný kód, aby se usnadnilo předávání testovacích objektů kontroleru.

Přidání injektáže závislostí

V současné době ProductController Třída je pevně kódován pro použití instance StoreAppContext třídy. K úpravě aplikace a odebrání této pevně zakódované závislosti použijete vzor označovaný jako injektáž závislostí. Když tuto závislost přerušíte, můžete při testování předat napodobený objekt.

Klikněte pravým tlačítkem na složku Models (Modely ) a přidejte nové rozhraní s názvem IStoreAppContext.

Nahraďte kód následujícím kódem.

using System;
using System.Data.Entity;

namespace StoreApp.Models
{
    public interface IStoreAppContext : IDisposable
    {
        DbSet<Product> Products { get; }
        int SaveChanges();
        void MarkAsModified(Product item);    
    }
}

Otevřete soubor StoreAppContext.cs a proveďte následující zvýrazněné změny. Důležité změny, které je potřeba si uvědomit, jsou:

  • Třída StoreAppContext implementuje rozhraní IStoreAppContext
  • Je implementována metoda 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;
        }
    }
}

Otevřete soubor ProductController.cs. Změňte existující kód tak, aby odpovídal zvýrazněnýmu kódu. Tyto změny narušují závislost na StoreAppContext a umožňují ostatním třídám předat jiný objekt pro třídu kontextu. Tato změna vám umožní předat testovací kontext během testů jednotek.

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
}

V ovládacím panelu ProductController musíte provést ještě jednu změnu. V metodě PutProduct nahraďte řádek, který nastavuje stav entity na změněno voláním 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
}

Sestavte řešení.

Teď jste připraveni nastavit projekt testu.

Instalace balíčků NuGet v testovacím projektu

Pokud k vytvoření aplikace použijete prázdnou šablonu, projekt testování jednotek (StoreApp.Tests) neobsahuje žádné nainstalované balíčky NuGet. Jiné šablony, například šablona webového rozhraní API, zahrnují některé balíčky NuGet v projektu testování jednotek. Pro účely tohoto kurzu musíte do testovacího projektu zahrnout balíček Entity Framework a balíček Microsoft ASP.NET Web API 2 Core.

Klikněte pravým tlačítkem na projekt StoreApp.Tests a vyberte Spravovat balíčky NuGet. Pokud chcete do projektu přidat balíčky, musíte vybrat projekt StoreApp.Tests.

správa balíčků

V online balíčcích vyhledejte a nainstalujte balíček EntityFramework (verze 6.0 nebo novější). Pokud se zdá, že balíček EntityFramework je už nainstalovaný, možná jste místo projektu StoreApp vybrali projekt StoreApp.

přidání Entity Frameworku

Vyhledejte a nainstalujte balíček Microsoft ASP.NET Web API 2 Core.

instalace balíčku Web API Core

Zavřete okno Spravovat balíčky NuGet.

Vytvoření kontextu testu

Do projektu testu přidejte třídu s názvem TestDbSet . Tato třída slouží jako základní třída pro testovací datovou sadu. Nahraďte kód následujícím kódem.

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();
        }
    }
}

Do testovacího projektu přidejte třídu s názvem TestProductDbSet , která obsahuje následující kód.

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());
        }
    }
}

Přidejte třídu s názvem TestStoreAppContext a nahraďte existující kód následujícím kódem.

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() { }
    }
}

Vytváření testů

Ve výchozím nastavení testovací projekt obsahuje prázdný testovací soubor s názvem UnitTest1.cs. Tento soubor zobrazuje atributy, které používáte k vytvoření testovacích metod. Pro účely tohoto kurzu můžete tento soubor odstranit, protože přidáte novou testovací třídu.

Přidejte třídu s názvem TestProductController do testovacího projektu. Nahraďte kód následujícím kódem.

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 };
        }
    }
}

Spouštění testů

Teď jste připraveni spustit testy. Všechny metody označené atributem TestMethod budou testovány . Z položky nabídky Test spusťte testy.

spouštění testů

Otevřete okno Průzkumníka testů a všimněte si výsledků testů.

výsledky testů