Unit Testing in Web API2 Using Entity Framework

Introduction

This article explains Unit Testing with Entity Framework in Web API 2, including how to modify the Scaffold Controller for passing the context object to the tests.

Step 1

Use the following procedure to create the application:

  • Start Visual Studio 2013.
  • From the Start Window select "New Project".
  • Select "Installed" -> "Template" -> "Visual C#" -> "Web" and select the "ASP.NET web application".

Select ASP.NET Web Application

  • Click on the "OK" button.
  • From the ASP.NET project window select "Empty" and check "Web API" and select "Unit Test" project.

Select Unit Test Project

  • Click on the "OK" button.

Step 2

Now add the model class to the project:

  • In the Solution Explorer.
  • Right-click on the Model Folder.
  • Select "Add" -> "Class".
  • Select "Installed" -> "Visual C#" and select "Class".

Add Model Class

  • Click on the "Add" button.

Add the following code in the class:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

namespace UnitTestMocking.Models

{

    public class Item

    {

        public int ID { getset; }

        public string ItemName { getset; }

        public decimal Cost { getset; }

    }

}

 

Step 3

Before adding the Web API Entity Framework we need to build the project:

  • Right-click on the Controller folder and select "Add" -> "New Scaffold Item".

Add Scaffold Item

  • The Select Web API2 Controller with Read, Write Entity Framework.

Add Web API2 Entity framework

  • Now select the Model Class and New Data Context.

Select Model class and Data Context

Add ENtity Framework Controller

  • Click on the "Add" button.

It automatically generates the code with methods for creating, fetching, deleting and updating instances of the Item class. It returns an instance of IHttpActionResult.

POST api/Item

        [ResponseType(typeof(Item))]

        public IHttpActionResult PostItem(Item item)

        {

            if (!ModelState.IsValid)

            {

                return BadRequest(ModelState);

            }

            db.Items.Add(item);

            db.SaveChanges();

            return CreatedAtRoute("DefaultApi"new { id = item.ID }, item);

        }

Step 4

Now add the dependency injection:

We use a pattern called dependency injection for modifying the application and remove the hard coded dependency.

  • Right-click on the Model Folder.
  • Select "add" -> "New Item" -> "Interface".
  • Click on Add button.

/Add ENtity Framework Controller

And add the following code:

using System;

using System.Collections.Generic;

using System.Data.Entity;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace UnitTestMocking.Models

{

   public interface IUnitTestMockingContext:IDisposable

    {

        DbSet<Item> Items { get; }

        int SaveChanges();

        void MarkAsModified(Item item);

    }

}

Now perform some changes in the "UnitTestMockingContext.cs" file. There are some important changes:

  • UnitTestMockingContext class implements with the interface "UnitTestMockingContext.cs".
  • Implement a new method "MarkAsModified".

Now the code looks like this:

using System;

using System.Collections.Generic;

using System.Data.Entity;

using System.Linq;

using System.Web;

namespace UnitTestMocking.Models

{

    public class UnitTestMockingContext : DbContext,IUnitTestMockingContext

    {

        public UnitTestMockingContext() : base("name=UnitTestMockingContext")

        {

        }

        public System.Data.Entity.DbSet<UnitTestMocking.Models.Item> Items { getset; }

            public void MarkAsModified(Item item)

            {

                Entry (item).State=EntityState.Modified;

            }

    }

}

 

 Now perform some changes in the ItemController class.

 

public class ItemController : ApiController

    {

        private IUnitTestMockingContext db = new UnitTestMockingContext();

        public ItemController()

        { }

        public ItemController(IUnitTestMockingContext context)

        {

            db = context;

        }

        // GET api/Item

        public IQueryable<Item> GetItems()

        {

            return db.Items;

        }

Change one line of code in the PutItem Method in the Item Controller class.

//db.Entry(item).State = EntityState.Modified;

            db.MarkAsModified(item);

Step 5

Now install some important packages in the "UnitTestMocking.Tests" project. Right-click on the test project and select "MAnages NuGet PAckage." and install two packages.

  • Entity Framework

Install Entity Framework

  • ASP.NET Web API2 Core.

Install Web API2 Core

Step 6

Create a test context. Add a class named TestDb to the Test project. This class works as a base class for the Teat project. Add a new class and write this code:

using System;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Data.Entity;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace UnitTestMocking.Tests

{

    public class TestDb<T>:DbSet<T>,IQueryable,IEnumerable<T>

        where T:class

    {

        ObservableCollection<T> Observ;

        IQueryable query;

        public TestDb()

        {

            Observ = new ObservableCollection<T>();

            query = Observ.AsQueryable();

        }

     public override T Add(T thing)

        {

            Observ.Add(thing);

            return thing;

        }

     public override T Remove(T thing)

        {

            Observ.Remove(thing);

            return thing;

        }

     public override T Attach(T thing)

        {

            Observ.Add(thing);

            return thing;

        }

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

        }

        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 Observ.GetEnumerator();

        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()

        {

            return Observ.GetEnumerator();

        }

    }

}

Now add a new class named "TestItemDbSet" and add the following code:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using UnitTestMocking.Models;

namespace UnitTestMocking.Tests

{

    class TestItemDbSet:TestDb<Item>

    {

        public override Item Find(params object[] keyValues)

        {

            return this.SingleOrDefault(item => item.ID == (int)keyValues.Single());

        }

    }

}

 

Now add a TestUnitTestMockingConext with the following code:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using UnitTestMocking.Models;

using System.Data.Entity;

namespace UnitTestMocking.Tests

{

    class TestUnitTestMockingConext:IUnitTestMockingContext

    {

public TestUnitTestMockingConext()

        {

            this.Items = new TestItemDbSet();

        }

        public DbSet<Item> Items { getset; }

        public int SaveChanges()

        {

            return 0;

        }

        public void MarkAsModified(Item item) { }

        public void Dispose() { }

    }

}

Now create a TestClass. We can see in our project that there is a class UnitTest1 that is generated by default. Now we delete this file and add a new test class.

Add a class name "TestItemController.cs" with this code:

using Microsoft.VisualStudio.TestTools.UnitTesting;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Text;

using System.Threading.Tasks;

using System.Web.Http.Results;

using UnitTestMocking.Controllers;

using UnitTestMocking.Models;

namespace UnitTestMocking.Tests

{

    [TestClass]

    public class TestItemController

    {

         [TestMethod]

        public void PostItem_ShouldReturnSameItem()

        {

            var controller = new ItemController(new TestUnitTestMockingConext());

            var item = GetDemoItem();

            var result =

                controller.PostItem(item) as CreatedAtRouteNegotiatedContentResult<Item>;

            Assert.IsNotNull(result);

            Assert.AreEqual(result.RouteName, "DefaultApi");

            Assert.AreEqual(result.RouteValues["id"], result.Content.ID);

            Assert.AreEqual(result.Content.ItemName, item.ItemName);

        }

        [TestMethod]

        public void PutItem_ShouldReturnStatusCode()

        {

            var controller = new ItemController(new TestUnitTestMockingConext());

            var item = GetDemoItem();

            var result = controller.PutItem(item.ID, item) as StatusCodeResult;

            Assert.IsNotNull(result);

            Assert.IsInstanceOfType(result, typeof(StatusCodeResult));

            Assert.AreEqual(HttpStatusCode.NoContent, result.StatusCode);

        }

        [TestMethod]

        public void PutItem_ShouldFail_WhenDifferentID()

        {

            var controller = new ItemController(new TestUnitTestMockingConext());

            var badresult = controller.PutItem(999, GetDemoItem());

            Assert.IsInstanceOfType(badresult, typeof(BadRequestResult));

        }

        [TestMethod]

        public void GetItem_ShouldReturnItemWithSameID()

        {

            var context = new TestUnitTestMockingConext();

            context.Items.Add(GetDemoItem());

            var controller = new ItemController(context);

            var result = controller.GetItem(3) as OkNegotiatedContentResult<Item>;

            Assert.IsNotNull(result);

            Assert.AreEqual(3, result.Content.ID);

        } 

        [TestMethod]

        public void GetItems_ShouldReturnAllItems()        {

            var context = new TestUnitTestMockingConext();

            context.Items.Add(new Item{ ID = 1, ItemName = "Demo1", Cost = 20 });

            context.Items.Add(new Item { ID = 2, ItemName = "Demo2", Cost = 30 });

            context.Items.Add(new Item { ID = 3, ItemName = "Demo3", Cost = 40 });

           var controller = new ItemController(context);

            var result = controller.GetItems() as TestItemDbSet;

            Assert.IsNotNull(result);

            Assert.AreEqual(3, result.Local.Count);

        }

        [TestMethod]

        public void DeleteItem_ShouldReturnOK()        {

            var context = new TestUnitTestMockingConext();

            var item = GetDemoItem();

            context.Items.Add(item);

            var controller = new ItemController(context);

            var result = controller.DeleteItem(3) as OkNegotiatedContentResult<Item>;

            Assert.IsNotNull(result);

            Assert.AreEqual(item.ID, result.Content.ID);

        }

    Item GetDemoItem()

        {

            return new Item() { ID = 3, ItemName = "Demo name", Cost = 5 };

        }

    }

}

 

Step 8

Now run the test:

  • Go to the Menu Bar and select "Test" -> "Run"  -> "All Test".

Run Test

  • Open the Test Explorer window and see the result:

Test Result