Birim testi ASP.NET Web API 2 ' de Entity Framework MockingMocking Entity Framework when Unit Testing ASP.NET Web API 2

Tom FitzMacken tarafındanby Tom FitzMacken

Tamamlanmış projeyi indirDownload Completed Project

Bu kılavuz ve uygulama, Entity Framework kullanan Web API 2 uygulamanız için birim testlerinin nasıl oluşturulacağını göstermektedir.This guidance and application demonstrate how to create unit tests for your Web API 2 application that uses the Entity Framework. Bağlam nesnesinin test için geçirilmesini ve Entity Framework birlikte çalışan test nesnelerinin nasıl oluşturulacağını etkinleştirmek için, yapı iskelesi denetleyicisinin nasıl değiştirileceğini gösterir.It shows how to modify the scaffolded controller to enable passing a context object for testing, and how to create test objects that work with Entity Framework.

ASP.NET Web API 'SI ile birim teste giriş için bkz. ASP.NET Web API 2 Ile birim testi.For an introduction to unit testing with ASP.NET Web API, see Unit Testing with ASP.NET Web API 2.

Bu öğreticide, ASP.NET Web API 'sinin temel kavramları hakkında bilgi sahibi olduğunuz varsayılır.This tutorial assumes you are familiar with the basic concepts of ASP.NET Web API. Tanıtım öğreticisi için bkz. ASP.NET Web API 2 Ile çalışmayabaşlama.For an introductory tutorial, see Getting Started with ASP.NET Web API 2.

Öğreticide kullanılan yazılım sürümleriSoftware versions used in the tutorial

Bu konudaIn this topic

Bu konu aşağıdaki bölümleri içermektedir:This topic contains the following sections:

ASP.NET Web API 2 Ile birim testindeadımları zaten tamamladıysanız, denetleyiciyi eklemebölümüne atlayabilirsiniz.If you have already completed the steps in Unit Testing with ASP.NET Web API 2, you can skip to the section Add the controller.

ÖnkoşullarPrerequisites

Visual Studio 2017 Community, Professional veya Enterprise EditionVisual Studio 2017 Community, Professional or Enterprise edition

Kodu indirinDownload code

Tamamlanmış projeyiindirin.Download the completed project. İndirilebilir proje, bu konu için birim test kodu ve birim testi ASP.NET Web API 2 konusu için içerir.The downloadable project includes unit test code for this topic and for the Unit Testing ASP.NET Web API 2 topic.

Birim testi projesiyle uygulama oluşturCreate application with unit test project

Uygulamanızı oluştururken bir birim testi projesi oluşturabilir veya var olan bir uygulamaya bir birim testi projesi ekleyebilirsiniz.You can either create a unit test project when creating your application or add a unit test project to an existing application. Bu öğretici, uygulamayı oluştururken bir birim testi projesi oluşturmayı gösterir.This tutorial shows creating a unit test project when creating the application.

Storeappadlı yeni bir ASP.NET Web uygulaması oluşturun.Create a new ASP.NET Web Application named StoreApp.

Yeni ASP.NET proje penceresinde boş şablonu seçin ve Web API 'si için klasörler ve çekirdek başvurular ekleyin.In the New ASP.NET Project windows, select the Empty template and add folders and core references for Web API. Birim testleri Ekle seçeneğini belirleyin.Select the Add unit tests option. Birim testi projesi, otomatik olarak Storeapp. Testsolarak adlandırılır.The unit test project is automatically named StoreApp.Tests. Bu adı koruyabilirsiniz.You can keep this name.

birim testi projesi oluştur

Uygulamayı oluşturduktan sonra, bunu iki proje- Storeapp ve Storeapp. Testsiçerdiğini görürsünüz.After creating the application, you will see it contains two projects - StoreApp and StoreApp.Tests.

Model sınıfı oluşturmaCreate the model class

StoreApp projenizde, Product.csadlı modeller klasörüne bir sınıf dosyası ekleyin.In your StoreApp project, add a class file to the Models folder named Product.cs. Dosyanın içeriğini aşağıdaki kodla değiştirin.Replace the contents of the file with the following code.

using System;

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

Çözümü derleyin.Build the solution.

Denetleyiciyi eklemeAdd the controller

Denetleyiciler klasörüne sağ tıklayın ve Ekle ve yeni yapı iskelesi öğesi' ni seçin.Right-click the Controllers folder and select Add and New Scaffolded Item. Entity Framework kullanarak, eylemlerle Web API 2 denetleyicisi ' ni seçin.Select Web API 2 Controller with actions, using Entity Framework.

Yeni denetleyici Ekle

Aşağıdaki değerleri ayarlayın:Set the following values:

  • Denetleyici adı: ProductControllerController name: ProductController
  • Model Sınıfı: ürünModel class: Product
  • Veri bağlamı sınıfı: [aşağıda görülen değerleri dolduran Yeni veri bağlam düğmesini seçin]Data context class: [Select New data context button which fills in the values seen below]

denetleyiciyi belirtin

Denetleyiciyi otomatik olarak oluşturulan kodla oluşturmak için Ekle ' ye tıklayın.Click Add to create the controller with automatically-generated code. Kod, ürün sınıfının örneklerini oluşturma, alma, güncelleştirme ve silme yöntemlerini içerir.The code includes methods for creating, retrieving, updating and deleting instances of the Product class. Aşağıdaki kod, ürün ekleme yöntemini gösterir.The following code shows the method for add a Product. Metodun bir ıhttpactionresultörneği döndürtiğine dikkat edin.Notice that the method returns an instance of 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, Web API 2 ' deki yeni özelliklerden biridir ve birim testi geliştirmeyi basitleştirir.IHttpActionResult is one of the new features in Web API 2, and it simplifies unit test development.

Bir sonraki bölümde, test nesnelerini denetleyiciye geçirmeyi kolaylaştırmak için oluşturulan kodu özelleştirecek olursunuz.In the next section, you will customize the generated code to facilitate passing test objects to the controller.

Bağımlılık ekleme eklemeAdd dependency injection

Şu anda, ProductController sınıfı StoreAppContext sınıfının bir örneğini kullanmak için sabit olarak kodlanmıştır.Currently, the ProductController class is hard-coded to use an instance of the StoreAppContext class. Uygulamanızı değiştirmek ve bu sabit kodlanmış bağımlılığı kaldırmak için bağımlılık ekleme adlı bir model kullanacaksınız.You will use a pattern called dependency injection to modify your application and remove that hard-coded dependency. Bu bağımlılığı bozarak, test ederken bir sahte nesne geçirebilirsiniz.By breaking this dependency, you can pass in a mock object when testing.

Modeller klasörüne sağ tıklayın ve Istoreappcontextadlı yeni bir arabirim ekleyin.Right-click the Models folder, and add a new interface named IStoreAppContext.

Kodu aşağıdaki kodla değiştirin.Replace the code with the following code.

using System;
using System.Data.Entity;

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

StoreAppContext.cs dosyasını açın ve aşağıdaki vurgulanan değişiklikleri yapın.Open the StoreAppContext.cs file and make the following highlighted changes. Nottaki önemli değişiklikler şunlardır:The important changes to note are:

  • StoreAppContext sınıfı ıtoreappcontext arabirimini uygularStoreAppContext class implements IStoreAppContext interface
  • MarkAsModified yöntemi uygulandıMarkAsModified method is implemented
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;
        }
    }
}

ProductController.cs dosyasını açın.Open the ProductController.cs file. Mevcut kodu vurgulanan kodla eşleşecek şekilde değiştirin.Change the existing code to match the highlighted code. Bu değişiklikler StoreAppContext üzerindeki bağımlılığı keser ve diğer sınıfların bağlam sınıfı için farklı bir nesneye geçmesini sağlar.These changes break the dependency on StoreAppContext and enable other classes to pass in a different object for the context class. Bu değişiklik, birim testleri sırasında bir test bağlamı geçirmenize olanak sağlar.This change will enable you to pass in a test context during unit tests.

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
}

ProductController 'da yapmanız gereken bir değişiklik daha vardır.There is one more change you must make in ProductController. Putproduct yönteminde, varlık durumunu ayarlayan satırı, MarkAsModified yöntemine yapılan bir çağrı ile değiştirin.In the PutProduct method, replace the line that sets the entity state to modified with a call to the MarkAsModified method.

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

Çözümü derleyin.Build the solution.

Şimdi test projesi ayarlamaya hazırsınız.You are now ready to set up the test project.

NuGet paketlerini test projesine yüklerInstall NuGet packages in test project

Bir uygulama oluşturmak için boş şablonu kullandığınızda, birim testi projesi (StoreApp. Tests) yüklü herhangi bir NuGet paketini içermez.When you use the Empty template to create an application, the unit test project (StoreApp.Tests) does not include any installed NuGet packages. Web API şablonu gibi diğer şablonlar, birim testi projesine bazı NuGet paketleri içerir.Other templates, such as the Web API template, include some NuGet packages in the unit test project. Bu öğreticide, Entity Framework paketini ve Microsoft ASP.NET Web API 2 Core paketini test projesine eklemeniz gerekir.For this tutorial, you must include the Entity Framework package and the Microsoft ASP.NET Web API 2 Core package to the test project.

StoreApp. Tests projesine sağ tıklayın ve NuGet Paketlerini Yönet' i seçin.Right-click the StoreApp.Tests project and select Manage NuGet Packages. Paketleri bu projeye eklemek için StoreApp. Tests projesini seçmelisiniz.You must select the StoreApp.Tests project to add the packages to that project.

Paketleri yönetme

Çevrimiçi paketlerden EntityFramework paketini bulun ve (sürüm 6,0 veya üzeri) yükler.From the Online packages, find and install the EntityFramework package (version 6.0 or later). EntityFramework paketinin zaten yüklü olduğu görüntülenirse StoreApp. Tests projesi yerine StoreApp projesini seçmiş olabilirsiniz.If it appears that the EntityFramework package is already installed, you may have selected the StoreApp project instead of the StoreApp.Tests project.

Entity Framework Ekle

Microsoft ASP.NET Web API 2 çekirdek paketini bulun ve yükler.Find and install Microsoft ASP.NET Web API 2 Core package.

Web API Core paketini yükler

NuGet Paketlerini Yönet penceresini kapatın.Close the Manage NuGet Packages window.

Test bağlamı oluşturCreate test context

Test projesine Testdbset adlı bir sınıf ekleyin.Add a class named TestDbSet to the test project. Bu sınıf, test veri kümesi için temel sınıf olarak hizmet verir.This class serves as the base class for your test data set. Kodu aşağıdaki kodla değiştirin.Replace the code with the following 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();
        }
    }
}

Aşağıdaki kodu içeren test projesine Testproductdbset adlı bir sınıf ekleyin.Add a class named TestProductDbSet to the test project which contains the following code.

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

Teststoreappcontext adlı bir sınıf ekleyin ve mevcut kodu aşağıdaki kodla değiştirin.Add a class named TestStoreAppContext and replace the existing code with the following 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() { }
    }
}

Test oluşturCreate tests

Varsayılan olarak, test projeniz UnitTest1.csadlı boş bir test dosyası içerir.By default, your test project includes an empty test file named UnitTest1.cs. Bu dosya, test yöntemleri oluşturmak için kullandığınız öznitelikleri gösterir.This file shows the attributes you use to create test methods. Bu öğreticide, yeni bir test sınıfı ekleyeceğiniz için bu dosyayı silebilirsiniz.For this tutorial, you can delete this file because you will add a new test class.

Test projesine Testproductcontroller adlı bir sınıf ekleyin.Add a class named TestProductController to the test project. Kodu aşağıdaki kodla değiştirin.Replace the code with the following 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 };
        }
    }
}

Testleri çalıştırmaRun tests

Şimdi testleri çalıştırmaya hazırsınız.You are now ready to run the tests. TestMethod özniteliğiyle işaretlenen tüm yöntem test edilecek.All of the method that are marked with the TestMethod attribute will be tested. Test menü öğesinden testleri çalıştırın.From the Test menu item, run the tests.

testleri çalıştırma

Test Gezgini penceresini açın ve testlerin sonuçlarına dikkat edin.Open the Test Explorer window, and notice the results of the tests.

test sonuçları