Entity Framework szimulálása az ASP.NET Web API 2 egységtesztelése során

készítette : Tom FitzMacken

Befejezett projekt letöltése

Ez az útmutató és alkalmazás bemutatja, hogyan hozhat létre egységteszteket az Entity Frameworkt használó web API 2-alkalmazáshoz. Bemutatja, hogyan módosíthatja az állványozott vezérlőt, hogy lehetővé tegye egy környezeti objektum teszteléshez való átadását, és hogyan hozhat létre olyan tesztobjektumokat, amelyek az Entity Framework használatával működnek.

A ASP.NET Webes API-val végzett egységtesztelésről a ASP.NET Web API 2-vel végzett egységtesztelés című témakörben olvashat.

Ez az oktatóanyag feltételezi, hogy ismeri ASP.NET Webes API alapfogalmait. Bevezető oktatóanyagért tekintse meg a ASP.NET Web API 2 használatának első lépéseit.

Az oktatóanyagban használt szoftververziók

Ebben a témakörben

Ez a témakör alábbi részeket tartalmazza:

Ha már elvégezte az egységtesztelés lépéseit ASP.NET Web API 2-vel, ugorjon a Vezérlő hozzáadása szakaszra.

Előfeltételek

Visual Studio 2017 Community, Professional vagy Enterprise kiadás

Kód letöltése

Töltse le a befejezett projektet. A letölthető projekt tartalmazza a témakör egységtesztelési kódját, valamint a Unit Testing ASP.NET Web API 2 témakörét.

Alkalmazás létrehozása egységtesztelési projekttel

Létrehozhat egy egységtesztelési projektet az alkalmazás létrehozásakor, vagy hozzáadhat egy egységtesztelési projektet egy meglévő alkalmazáshoz. Ez az oktatóanyag egy egységteszt-projekt létrehozását mutatja be az alkalmazás létrehozásakor.

Hozzon létre egy StoreApp nevű új ASP.NET webalkalmazást.

Az Új ASP.NET Projektablakban válassza ki az Üres sablont, és adjon hozzá mappákat és alapvető hivatkozásokat a Webes API-hoz. Válassza az Egységtesztek hozzáadása lehetőséget. Az egységtesztelési projekt neve automatikusan StoreApp.Tests. Ezt a nevet megtarthatja.

egységtesztelési projekt létrehozása

Az alkalmazás létrehozása után látni fogja, hogy két projektet tartalmaz : StoreApp és StoreApp.Tests.

A modellosztály létrehozása

A StoreApp-projektben adjon hozzá egy osztályfájlt a Product.cs nevű Models mappához. Cserélje le a fájl tartalmát a következő kódra.

using System;

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

Hozza létre a megoldást.

A vezérlő hozzáadása

Kattintson a jobb gombbal a Vezérlők mappára, és válassza a Hozzáadás és az Új állványozott elem lehetőséget. Válassza ki a Web API 2 Vezérlőt műveletekkel az Entity Framework használatával.

új vezérlő hozzáadása

Állítsa be a következő értékeket:

  • Vezérlő neve: ProductController
  • Modellosztály: Termék
  • Adatkörnyezeti osztály: [Válassza ki az Új adatkörnyezet gombot, amely kitölti az alábbi értékeket]

vezérlő megadása

A Hozzáadás gombra kattintva automatikusan generált kóddal hozhatja létre a vezérlőt. A kód a termékosztály példányainak létrehozására, beolvasására, frissítésére és törlésére szolgáló módszereket tartalmaz. Az alábbi kód egy termék hozzáadásának módját mutatja be. Figyelje meg, hogy a metódus az IHttpActionResult egy példányát adja vissza.

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

Az IHttpActionResult a Web API 2 egyik új funkciója, és leegyszerűsíti az egységtesztek fejlesztését.

A következő szakaszban testre szabja a létrehozott kódot, hogy megkönnyítse a tesztobjektumok vezérlőnek való átadását.

Függőséginjektálás hozzáadása

A ProductController osztály jelenleg nehezen kódolt a StoreAppContext osztály egy példányának használatához. Egy függőséginjektálás nevű mintát fog használni az alkalmazás módosításához és a szigorúan kódolt függőség eltávolításához. Ha megszakítja ezt a függőséget, a tesztelés során átadhat egy modellobjektumot.

Kattintson a jobb gombbal a Modellek mappára, és adjon hozzá egy új, IStoreAppContext nevű felületet.

Cserélje le a kódot a következő kódra.

using System;
using System.Data.Entity;

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

Nyissa meg a StoreAppContext.cs fájlt, és végezze el a következő kiemelt módosításokat. A fontos változások a következők:

  • A StoreAppContext osztály implementálja az IStoreAppContext felületet
  • A MarkAsModified metódus implementálva lett
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;
        }
    }
}

Nyissa meg a ProductController.cs fájlt. Módosítsa a meglévő kódot úgy, hogy megfeleljen a kiemelt kódnak. Ezek a módosítások megszakítják a StoreAppContext függőségét, és lehetővé teszik, hogy más osztályok egy másik objektumot adjanak át a környezeti osztály számára. Ez a módosítás lehetővé teszi egy tesztkörnyezet átadását az egységtesztek sorá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
}

A ProductControllerben még egy módosítást kell végrehajtania. A PutProduct metódusban cserélje le az entitás állapotát módosító sort a MarkAsModified metódus hívására.

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

Hozza létre a megoldást.

Most már készen áll a tesztprojekt beállítására.

NuGet-csomagok telepítése tesztprojektben

Amikor az Üres sablont használja egy alkalmazás létrehozásához, az egységtesztelési projekt (StoreApp.Tests) nem tartalmaz telepített NuGet-csomagokat. Más sablonok, például a Webes API-sablon, tartalmaznak néhány NuGet-csomagot az egységtesztelési projektben. Ebben az oktatóanyagban szerepelnie kell az Entity Framework csomagnak és a Microsoft ASP.NET Web API 2 Core csomagnak a tesztprojektbe.

Kattintson a jobb gombbal a StoreApp.Tests projektre, és válassza a NuGet-csomagok kezelése lehetőséget. A csomagokat a projekthez való hozzáadásához ki kell választania a StoreApp.Tests projektet.

csomagok kezelése

Az Online csomagokban keresse meg és telepítse az EntityFramework csomagot (6.0-s vagy újabb verzió). Ha úgy tűnik, hogy az EntityFramework csomag már telepítve van, előfordulhat, hogy a StoreApp.Tests projekt helyett a StoreApp projektet választotta.

Entity Framework hozzáadása

Keresse meg és telepítse a Microsoft ASP.NET Web API 2 Core-csomagot.

a webes API core-csomag telepítése

Zárja be a NuGet-csomagok kezelése ablakot.

Tesztkörnyezet létrehozása

Adjon hozzá egy TestDbSet nevű osztályt a tesztprojekthez. Ez az osztály szolgál a tesztadatkészlet alaposztályaként. Cserélje le a kódot a következő kódra.

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

Adjon hozzá egy TestProductDbSet nevű osztályt a tesztprojekthez, amely az alábbi kódot tartalmazza.

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

Adjon hozzá egy TestStoreAppContext nevű osztályt, és cserélje le a meglévő kódot a következő kódra.

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

Tesztek létrehozása

Alapértelmezés szerint a tesztprojekt tartalmaz egy üres, UnitTest1.cs nevű tesztfájlt. Ez a fájl a tesztelési módszerek létrehozásához használt attribútumokat jeleníti meg. Ebben az oktatóanyagban törölheti ezt a fájlt, mert új tesztosztályt fog hozzáadni.

Adjon hozzá egy TestProductController nevű osztályt a tesztprojekthez. Cserélje le a kódot a következő kódra.

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

Tesztek futtatása

Most már készen áll a tesztek futtatására. A TestMethod attribútummal megjelölt összes metódust tesztelni fogjuk. A Teszt menüelemből futtassa a teszteket.

tesztek futtatása

Nyissa meg a Test Explorer ablakot, és figyelje meg a tesztek eredményeit.

teszteredmények