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:

Step 2

Now add the model class to the project:

Add the following code in the class:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. namespace UnitTestMocking.Models
  6. {
  7. public class Item
  8. {
  9. public int ID { get; set; }
  10. public string ItemName { get; set; }
  11. public decimal Cost { get; set; }
  12. }
  13. }
Step 3

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

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

  1. POST api/Item
  2. [ResponseType(typeof(Item))]
  3. public IHttpActionResult PostItem(Item item)
  4. {
  5. if (!ModelState.IsValid)
  6. {
  7. return BadRequest(ModelState);
  8. }
  9. db.Items.Add(item);
  10. db.SaveChanges();
  11. return CreatedAtRoute("DefaultApi", new { id = item.ID }, item);
  12. }

Step 4

Now add the dependency injection:

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

And add the following code:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data.Entity;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace UnitTestMocking.Models
  8. {
  9. public interface IUnitTestMockingContext:IDisposable
  10. {
  11. DbSet<Item> Items { get; }
  12. int SaveChanges();
  13. void MarkAsModified(Item item);
  14. }
  15. }

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

Now the code looks like this:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data.Entity;
  4. using System.Linq;
  5. using System.Web;
  6. namespace UnitTestMocking.Models
  7. {
  8. public class UnitTestMockingContext : DbContext,IUnitTestMockingContext
  9. {
  10. public UnitTestMockingContext() : base("name=UnitTestMockingContext")
  11. {
  12. }
  13. public System.Data.Entity.DbSet<UnitTestMocking.Models.Item> Items { get; set; }
  14. public void MarkAsModified(Item item)
  15. {
  16. Entry (item).State=EntityState.Modified;
  17. }
  18. }
  19. }
Now perform some changes in the ItemController class.
  1. public class ItemController : ApiController
  2. {
  3. private IUnitTestMockingContext db = new UnitTestMockingContext();
  4. public ItemController()
  5. { }
  6. public ItemController(IUnitTestMockingContext context)
  7. {
  8. db = context;
  9. }
  10. // GET api/Item
  11. public IQueryable<Item> GetItems()
  12. {
  13. return db.Items;
  14. }
  15. }

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

  1. //db.Entry(item).State = EntityState.Modified;
  2. 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.

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:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Data.Entity;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace UnitTestMocking.Tests
  9. {
  10. public class TestDb<T>:DbSet<T>,IQueryable,IEnumerable<T>
  11. where T:class
  12. {
  13. ObservableCollection<T> Observ;
  14. IQueryable query;
  15. public TestDb()
  16. {
  17. Observ = new ObservableCollection<T>();
  18. query = Observ.AsQueryable();
  19. }
  20. public override T Add(T thing)
  21. {
  22. Observ.Add(thing);
  23. return thing;
  24. }
  25. public override T Remove(T thing)
  26. {
  27. Observ.Remove(thing);
  28. return thing;
  29. }
  30. public override T Attach(T thing)
  31. {
  32. Observ.Add(thing);
  33. return thing;
  34. }
  35. public override T Create()
  36. {
  37. return Activator.CreateInstance<T>();
  38. }
  39. public override TDerivedEntity Create<TDerivedEntity>()
  40. {
  41. return Activator.CreateInstance<TDerivedEntity>();
  42. }
  43. public override ObservableCollection<T> Local
  44. {
  45. get { return new ObservableCollection<T>(Observ); }
  46. }
  47. Type IQueryable.ElementType
  48. {
  49. get { return query.ElementType; }
  50. }
  51. System.Linq.Expressions.Expression IQueryable.Expression
  52. {
  53. get { return query.Expression; }
  54. }
  55. IQueryProvider IQueryable.Provider
  56. {
  57. get { return query.Provider; }
  58. }
  59. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  60. {
  61. return Observ.GetEnumerator();
  62. }
  63. IEnumerator<T> IEnumerable<T>.GetEnumerator()
  64. {
  65. return Observ.GetEnumerator();
  66. }
  67. }
  68. }

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using UnitTestMocking.Models;
  7. namespace UnitTestMocking.Tests
  8. {
  9. class TestItemDbSet:TestDb<Item>
  10. {
  11. public override Item Find(params object[] keyValues)
  12. {
  13. return this.SingleOrDefault(item => item.ID == (int)keyValues.Single());
  14. }
  15. }
  16. }

Now add a TestUnitTestMockingConext with the following code:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using UnitTestMocking.Models;
  7. using System.Data.Entity;
  8. namespace UnitTestMocking.Tests
  9. {
  10. class TestUnitTestMockingConext:IUnitTestMockingContext
  11. {
  12. public TestUnitTestMockingConext()
  13. {
  14. this.Items = new TestItemDbSet();
  15. }
  16. public DbSet<Item> Items { get; set; }
  17. public int SaveChanges()
  18. {
  19. return 0;
  20. }
  21. public void MarkAsModified(Item item) { }
  22. public void Dispose() { }
  23. }
  24. }

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:

  1. using Microsoft.VisualStudio.TestTools.UnitTesting;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using System.Web.Http.Results;
  9. using UnitTestMocking.Controllers;
  10. using UnitTestMocking.Models;
  11. namespace UnitTestMocking.Tests
  12. {
  13. [TestClass]
  14. public class TestItemController
  15. {
  16. [TestMethod]
  17. public void PostItem_ShouldReturnSameItem()
  18. {
  19. var controller = new ItemController(new TestUnitTestMockingConext());
  20. var item = GetDemoItem();
  21. var result =
  22. controller.PostItem(item) as CreatedAtRouteNegotiatedContentResult<Item>;
  23. Assert.IsNotNull(result);
  24. Assert.AreEqual(result.RouteName, "DefaultApi");
  25. Assert.AreEqual(result.RouteValues["id"], result.Content.ID);
  26. Assert.AreEqual(result.Content.ItemName, item.ItemName);
  27. }
  28. [TestMethod]
  29. public void PutItem_ShouldReturnStatusCode()
  30. {
  31. var controller = new ItemController(new TestUnitTestMockingConext());
  32. var item = GetDemoItem();
  33. var result = controller.PutItem(item.ID, item) as StatusCodeResult;
  34. Assert.IsNotNull(result);
  35. Assert.IsInstanceOfType(result, typeof(StatusCodeResult));
  36. Assert.AreEqual(HttpStatusCode.NoContent, result.StatusCode);
  37. }
  38. [TestMethod]
  39. public void PutItem_ShouldFail_WhenDifferentID()
  40. {
  41. var controller = new ItemController(new TestUnitTestMockingConext());
  42. var badresult = controller.PutItem(999, GetDemoItem());
  43. Assert.IsInstanceOfType(badresult, typeof(BadRequestResult));
  44. }
  45. [TestMethod]
  46. public void GetItem_ShouldReturnItemWithSameID()
  47. {
  48. var context = new TestUnitTestMockingConext();
  49. context.Items.Add(GetDemoItem());
  50. var controller = new ItemController(context);
  51. var result = controller.GetItem(3) as OkNegotiatedContentResult<Item>;
  52. Assert.IsNotNull(result);
  53. Assert.AreEqual(3, result.Content.ID);
  54. }
  55. [TestMethod]
  56. public void GetItems_ShouldReturnAllItems()
  57. {
  58. var context = new TestUnitTestMockingConext();
  59. context.Items.Add(new Item{ ID = 1, ItemName = "Demo1", Cost = 20 });
  60. context.Items.Add(new Item { ID = 2, ItemName = "Demo2", Cost = 30 });
  61. context.Items.Add(new Item { ID = 3, ItemName = "Demo3", Cost = 40 });
  62. var controller = new ItemController(context);
  63. var result = controller.GetItems() as TestItemDbSet;
  64. Assert.IsNotNull(result);
  65. Assert.AreEqual(3, result.Local.Count);
  66. }
  67. [TestMethod]
  68. public void DeleteItem_ShouldReturnOK()
  69. {
  70. var context = new TestUnitTestMockingConext();
  71. var item = GetDemoItem();
  72. context.Items.Add(item);
  73. var controller = new ItemController(context);
  74. var result = controller.DeleteItem(3) as OkNegotiatedContentResult<Item>;
  75. Assert.IsNotNull(result);
  76. Assert.AreEqual(item.ID, result.Content.ID);
  77. }
  78. Item GetDemoItem()
  79. {
  80. return new Item() { ID = 3, ItemName = "Demo name", Cost = 5 };
  81. }
  82. }
  83. }
Step 8

Now run the test: