Entity Framework 머킹 때 단위 테스트 ASP.NET Web API 2Mocking Entity Framework when Unit Testing ASP.NET Web API 2

Tom FitzMackenby Tom FitzMacken

완료 된 프로젝트 다운로드Download Completed Project

이 지침과 응용 프로그램에는 Entity Framework를 사용 하는 Web API 2 응용 프로그램에 대 한 단위 테스트를 만드는 방법을 보여 줍니다.This guidance and application demonstrate how to create unit tests for your Web API 2 application that uses the Entity Framework. 테스트에 대 한 컨텍스트 개체를 전달 하도록 설정 하는 스 캐 폴드 된 컨트롤러를 수정 하는 방법 및 Entity Framework와 함께 작동 하는 테스트 개체를 만드는 방법을 보여 줍니다.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를 사용한 단위 테스트 소개를 참조 하세요 ASP.NET Web API 2를 사용 하 여 단위 테스트합니다.For an introduction to unit testing with ASP.NET Web API, see Unit Testing with ASP.NET Web API 2.

이 자습서에서는 ASP.NET Web API의 기본 개념에 익숙하다고 가정 합니다.This tutorial assumes you are familiar with the basic concepts of ASP.NET Web API. 입문 용 자습서는를 참조 하세요 ASP.NET Web API 2 시작합니다.For an introductory tutorial, see Getting Started with ASP.NET Web API 2.

이 자습서에 사용 되는 소프트웨어 버전Software versions used in the tutorial

항목 내용In this topic

이 항목에는 다음과 같은 단원이 포함되어 있습니다.This topic contains the following sections:

단계를 이미 완료 하는 경우 ASP.NET Web API 2를 사용 하 여 단위 테스트, 섹션으로 건너뛸 수 있습니다 컨트롤러를 추가합니다.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.

전제 조건Prerequisites

Visual Studio 2017 Community, Professional 또는 Enterprise editionVisual Studio 2017 Community, Professional or Enterprise edition

코드 다운로드Download code

다운로드 합니다 완성 된 프로젝트합니다.Download the completed project. 다운로드 가능한 프로젝트 및이 항목에 대 한 단위 테스트 코드를 포함 합니다 단위 테스트 ASP.NET Web API 2 항목입니다.The downloadable project includes unit test code for this topic and for the Unit Testing ASP.NET Web API 2 topic.

단위 테스트 프로젝트를 사용 하 여 응용 프로그램 만들기Create application with unit test project

응용 프로그램을 만들 때 단위 테스트 프로젝트 만들기 또는 기존 응용 프로그램에 단위 테스트 프로젝트를 추가 합니다.You can either create a unit test project when creating your application or add a unit test project to an existing application. 이 자습서에서는 응용 프로그램을 만들 때 단위 테스트 프로젝트 만들기를 보여 줍니다.This tutorial shows creating a unit test project when creating the application.

명명 된 새 ASP.NET 웹 응용 프로그램 만들기 StoreApp합니다.Create a new ASP.NET Web Application named StoreApp.

새 ASP.NET 프로젝트 창에서 선택 합니다 템플릿 폴더를 추가 하 고 웹 API에 대 한 참조를 핵심입니다.In the New ASP.NET Project windows, select the Empty template and add folders and core references for Web API. 선택 된 단위 테스트 추가 옵션입니다.Select the Add unit tests option. 단위 테스트 프로젝트 이름은 자동으로 StoreApp.Tests합니다.The unit test project is automatically named StoreApp.Tests. 이 이름을 유지할 수 있습니다.You can keep this name.

단위 테스트 프로젝트 만들기

응용 프로그램을 만든 후 보면 두 개의 프로젝트-포함 StoreApp 하 고 StoreApp.Tests합니다.After creating the application, you will see it contains two projects - StoreApp and StoreApp.Tests.

모델 클래스 만들기Create the model class

StoreApp 프로젝트에서 클래스 파일을 추가 합니다 모델 폴더가 Product.cs합니다.In your StoreApp project, add a class file to the Models folder named Product.cs. 파일의 내용을 다음 코드로 바꿉니다.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; }
    }
}

솔루션을 빌드합니다.Build the solution.

컨트롤러 추가Add the controller

컨트롤러 폴더를 마우스 오른쪽 단추로 누르고 추가 하 고 새 스 캐 폴드 된 항목합니다.Right-click the Controllers folder and select Add and New Scaffolded Item. Entity Framework를 사용 하 여 작업을 사용 하 여 Web API 2 컨트롤러를 선택 합니다.Select Web API 2 Controller with actions, using Entity Framework.

새 컨트롤러 추가

다음 값을 설정합니다.Set the following values:

  • 컨트롤러 이름: ProductControllerController name: ProductController
  • 모델 클래스: 제품Model class: Product
  • 데이터 컨텍스트 클래스: [선택 새 데이터 컨텍스트에 아래와 값을 채웁니다 단추]Data context class: [Select New data context button which fills in the values seen below]

컨트롤러를 지정 합니다.

클릭 추가 자동으로 생성 된 코드를 사용 하 여 컨트롤러를 만들려고 합니다.Click Add to create the controller with automatically-generated code. 코드 생성, 검색, 업데이트 및 Product 클래스의 인스턴스를 삭제 하는 메서드를 포함 합니다.The code includes methods for creating, retrieving, updating and deleting instances of the Product class. 다음 코드는 메서드를 보여 줍니다.에 대 한 제품을 추가 합니다.The following code shows the method for add a Product. 메서드는 인스턴스를 반환 하는 알 수 있습니다 IHttpActionResult합니다.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의 새로운 기능 중 하나인 및 단위 테스트 개발을 간소화 합니다.IHttpActionResult is one of the new features in Web API 2, and it simplifies unit test development.

다음 섹션에서는 용이 하 게 생성된 된 코드를 사용자 지정은 컨트롤러에 테스트 개체를 전달 합니다.In the next section, you will customize the generated code to facilitate passing test objects to the controller.

추가 종속성 주입Add dependency injection

현재 ProductController 클래스 StoreAppContext 클래스의 인스턴스를 사용 하도록 하드 코드 되어 있습니다.Currently, the ProductController class is hard-coded to use an instance of the StoreAppContext class. 종속성 주입 이라고 하는 패턴을 사용 하 여 응용 프로그램을 수정 하는 하드 코드 된 종속성을 제거 됩니다.You will use a pattern called dependency injection to modify your application and remove that hard-coded dependency. 이 종속성을 분리 하 여 테스트할 때 모의 개체에 전달할 수 있습니다.By breaking this dependency, you can pass in a mock object when testing.

마우스 오른쪽 단추로 클릭 합니다 모델 폴더를 라는 새 인터페이스를 추가 하 고 IStoreAppContext합니다.Right-click the Models folder, and add a new interface named IStoreAppContext.

코드를 다음 코드로 바꿉니다.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 파일을 열고 다음 강조 표시 된 변경 내용을 확인 합니다.Open the StoreAppContext.cs file and make the following highlighted changes. 에 중요 한 변경 내용은 다음과 같습니다.The important changes to note are:

  • StoreAppContext 클래스 IStoreAppContext 인터페이스를 구현합니다.StoreAppContext class implements IStoreAppContext interface
  • MarkAsModified 메서드가 구현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 파일을 엽니다.Open the ProductController.cs file. 강조 표시 된 코드와 일치 하도록 기존 코드를 변경 합니다.Change the existing code to match the highlighted code. 이러한 변경 내용은 StoreAppContext에서 종속성을 중단 하 고 컨텍스트 클래스에 대 한 다른 개체에 전달할 다른 클래스를 사용 하도록 설정 합니다.These changes break the dependency on StoreAppContext and enable other classes to pass in a different object for the context class. 이 변경을 사용 하면 단위 테스트 중 테스트 컨텍스트를 전달할 수 있습니다.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 해야 있습니다.There is one more change you must make in ProductController. PutProduct 메서드를 대체 엔터티 상태를 설정 하는 줄 MarkAsModified 메서드를 호출 하 여 수정 합니다.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
}

솔루션을 빌드합니다.Build the solution.

이제 테스트 프로젝트를 설정할 준비가 되었습니다.You are now ready to set up the test project.

테스트 프로젝트에서 NuGet 패키지를 설치 합니다.Install NuGet packages in test project

빈 템플릿을 사용 하 여 응용 프로그램을 만들 때 단위 테스트 프로젝트 (StoreApp.Tests)는 설치 된 모든 NuGet 패키지를 포함 하지 않습니다.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 템플릿과 같은 다른 템플릿을 단위 테스트 프로젝트에 일부 NuGet 패키지를 포함합니다.Other templates, such as the Web API template, include some NuGet packages in the unit test project. 이 자습서에서는 Entity Framework 패키지 및 Microsoft ASP.NET 웹 API 2 코어 패키지 테스트 프로젝트를 포함 해야 합니다.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 프로젝트를 마우스 오른쪽 단추로 누르고 NuGet 패키지 관리합니다.Right-click the StoreApp.Tests project and select Manage NuGet Packages. 해당 프로젝트에 패키지를 추가 하려면 StoreApp.Tests 프로젝트를 선택 해야 합니다.You must select the StoreApp.Tests project to add the packages to that project.

패키지 관리

패키지를 온라인에서 찾아 EntityFramework 패키지 (버전 6.0 이상)를 설치 합니다.From the Online packages, find and install the EntityFramework package (version 6.0 or later). EntityFramework 패키지가 이미 설치 되어 있는지, 표시 되는 경우 StoreApp.Tests 프로젝트 대신 StoreApp 프로젝트를 선택한 수 있습니다.If it appears that the EntityFramework package is already installed, you may have selected the StoreApp project instead of the StoreApp.Tests project.

add Entity Framework

찾기 및 Microsoft ASP.NET 웹 API 2 코어 패키지를 설치 합니다.Find and install Microsoft ASP.NET Web API 2 Core package.

웹 api core 패키지를 설치 합니다.

NuGet 패키지 관리 창을 닫습니다.Close the Manage NuGet Packages window.

테스트 컨텍스트 만들기Create test context

라는 클래스를 추가 TestDbSet 테스트 프로젝트에 있습니다.Add a class named TestDbSet to the test project. 이 클래스는 테스트 데이터 집합에 대 한 기본 클래스로 사용 됩니다.This class serves as the base class for your test data set. 코드를 다음 코드로 바꿉니다.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();
        }
    }
}

라는 클래스를 추가 TestProductDbSet 다음 코드를 포함 하는 테스트 프로젝트에 있습니다.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 기존 코드를 다음 코드로 바꿉니다.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() { }
    }
}

테스트 만들기Create tests

기본적으로 테스트 프로젝트에 빈 테스트 파일인 UnitTest1.cs합니다.By default, your test project includes an empty test file named UnitTest1.cs. 이 파일을 사용 하면 테스트 메서드를 만들 특성을 보여 줍니다.This file shows the attributes you use to create test methods. 이 자습서에서는 새 테스트 클래스를 추가 하기 때문에이 파일을 삭제할 수 있습니다.For this tutorial, you can delete this file because you will add a new test class.

라는 클래스를 추가 TestProductController 테스트 프로젝트에 있습니다.Add a class named TestProductController to the test project. 코드를 다음 코드로 바꿉니다.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 };
        }
    }
}

테스트 실행Run tests

이제 테스트를 실행할 준비가 되었습니다.You are now ready to run the tests. 로 표시 되는 메서드의 모든 합니다 TestMethod 특성을 테스트 합니다.All of the method that are marked with the TestMethod attribute will be tested. 테스트 메뉴 항목, 테스트를 실행 합니다.From the Test menu item, run the tests.

테스트 실행

열기는 테스트 탐색기 창 테스트의 결과 확인 합니다.Open the Test Explorer window, and notice the results of the tests.

테스트 결과