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".
- Click on the "OK" button.
- From the ASP.NET project window select "Empty" and check "Web API" and 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".
- 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 { get; set; }
- public string ItemName { get; set; }
- public decimal Cost { get; set; }
- }
- }
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".
- The Select Web API2 Controller with Read, Write Entity Framework.
- Now select the Model Class and New Data Context.
- 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.
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 { get; set; }
- 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;
- }
-
- public IQueryable<Item> GetItems()
- {
- return db.Items;
- }
- }
Change one line of code in the PutItem Method in the Item Controller class.
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
- ASP.NET 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 { get; set; }
- 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".
- Open the Test Explorer window and see the result: