Napodobování Entity Framework při jednicovém testování ASP.NET Web API 2

Tom FitzMacken

Stáhnout dokončený projekt

Tyto pokyny a aplikace ukazují, jak vytvořit testy jednotek pro vaši 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 Framework.

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

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

Verze softwaru používané v tomto kurzu

V tomto tématu

Toto téma obsahuje následující části:

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

Předpoklady

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 jednotkových testů pro toto téma a pro téma jednotkového testování ASP.NET Web API 2.

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

Projekt testování jednotek můžete vytvořit při vytváření aplikace nebo přidat projekt testu 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ý ASP.NET Projectu 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 jednotkových testů se automaticky jmenuje StoreApp.Tests. Tento název můžete zachovat.

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 myši 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: Produkt
  • cs-CZ: Třída kontextu dat: [Vyberte tlačítko Nový datový kontext, což vyplní hodnoty níže]

zadat řadič

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 vrací 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, abyste usnadnili předávání testovacích objektů kontroleru.

Přidat injektování závislostí

Třída ProductController je v současné době pevně zakódována pro použití instance StoreAppContext třídy. Pomocí vzoru označovaného jako injektáž závislostí upravíte aplikaci a odeberete tuto pevně zakódovanou 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 myši na složku Models 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. Je důležité si všimnout následujících změn:

  • Třída StoreAppContext implementuje rozhraní IStoreAppContext
  • Implementovaná 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ý kód. Tyto změny přeruší závislost na StoreAppContext a umožní ostatním třídám předat jiný objekt pro třídu kontextu. Tato změna vám umožní předat kontext pro jednotkové testy během testování.

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
}

Existuje ještě jedna změna, kterou musíte provést v ProductController. V PutProduct metoda nahraďte řádek, který nastaví stav entity na změněný voláním MarkAsModified metoda.

// 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 testovací projekt.

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

Pokud k vytvoření aplikace použijete prázdnou šablonu, projekt testů jednotek (StoreApp.Tests) neobsahuje žádné nainstalované balíčky NuGet. Mezi další šablony, jako je šablona webového rozhraní API, patří 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. Chcete-li do projektu přidat balíčky, musíte vybrat projekt StoreApp.Tests.

správa balíčků

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

přidání Entity Frameworku

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

Instalace balíčku Jádra webového rozhraní API

Zavřete okno Spravovat balíčky NuGet.

Vytvoření kontextu testu

Přidejte třídu s názvem TestDbSet do testovacího projektu. 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();
        }
    }
}

Přidejte třídu s názvem TestProductDbSet do testovacího projektu, 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í projekt testů 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 do testovacího projektu třídu s názvem TestProductController . 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. Testuje se všechna metoda označená atributem TestMethod . 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 testu