.NET 6 Web API Mocking DbContext & DbSet using Moq
A Guide to create mock DbContext and DbSet in entity framework.
In this article, we will explore how to create mock implementations for the DbContext and DbSet classes in Entity Framework, specifically targeting .NET 6 Web API applications. By following this step-by-step guide, you’ll gain insights into setting up a robust testing environment and writing effective unit tests for your EF-based data access layer.
Steps:
Create an ASP.NET Core Web API application.
Here, We are using an SQLite database. So we have installed the Entity framework package for SQLite. Install the following Nuget packages.
Create a Database folder under the API project and Keep your database file there. (Note: Usually, we won't keep Database file in the application itself. I kept here just for demonstration.)
In appsettings.json, Update the connection string.
{ "ConnectionStrings": "Data Source=.\\Database\\User.db;" }
Create the following class
DataContext.cs - /DataAccessLayer/Context/DataContext.cs.
public class DataContext:DbContext { public DataContext() { public DataContext(DbContextOptions<DataContext> options) : base(options) { } public virtual DbSet<Employee> M_Employee { get; set; } } }
Employee.cs - /DataAccessLayer/Models/Employee.cs - Employee Table Model
[Table("M_Employee")] public class Employee { [Key] public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
IEmployeeRepository.cs - DataAccessLayer/Interface/IEmployeeRepository.cs - Interface for the Employee Repository with CURD Method
public interface IEmployeeRepository { IList<Employee> GetEmployees(); Employee GetEmployeeById(int id); bool AddEmployee(Employee employee); bool DeleteEmployee(Employee employee); bool UpdateEmployee(Employee employee); }
EmployeeRepository.cs -DataAccessLayer/Repository/EmployeeRepository.cs
public class EmployeeRepository : IEmployeeRepository { private readonly DataContext _dataContext; public EmployeeRepository(DataContext dataContext) { _dataContext = dataContext; } public bool AddEmployee(Employee employee) { if (_dataContext.M_Employee == null) return false; if(_dataContext.M_Employee.Any(x=>x.Id== employee.Id)) { return false; } _dataContext.M_Employee.Add(employee); _dataContext.SaveChanges(); return true; } public bool DeleteEmployee(Employee employee) { if (_dataContext.M_Employee == null) return false; if (!_dataContext.M_Employee.Any(x => x.Id == employee.Id)) { return false; } _dataContext.M_Employee.Remove(employee); _dataContext.SaveChanges(); return true; } public Employee GetEmployeeById(int id) { if (_dataContext.M_Employee == null) return null; return _dataContext.M_Employee.FirstOrDefault(x => x.Id == id); } public IList<Employee> GetEmployees() { if (_dataContext.M_Employee == null) return new List<Employee>(); return _dataContext.M_Employee.ToList(); } public bool UpdateEmployee(Employee employee) { if (_dataContext.M_Employee == null) return false; if (!_dataContext.M_Employee.Any(x => x.Id == employee.Id)) { return false; } _dataContext.M_Employee.Update(employee); _dataContext.SaveChanges(); return true; } }
Create a Controller EmployeeController.cs.
private readonly IEmployeeRepository _employeeRepository; public EmployeeController(IEmployeeRepository employeeRepository) { _employeeRepository = employeeRepository; } [HttpGet] [Route("[action]")] public IActionResult GetEmployees() { return Ok(_employeeRepository.GetEmployees()); } [HttpGet] [Route("[action]")] public IActionResult GetEmployee([FromQuery] int id) { Employee employee = _employeeRepository.GetEmployeeById(id); if (employee != null) return Ok(employee); else return BadRequest("Employee is not found"); } [HttpPost] [Route("[action]")] public IActionResult AddEmployee(Employee employee) { if (!ModelState.IsValid) { return BadRequest("Employee Model is Invalide"); } if (_employeeRepository.AddEmployee(employee)) { return Ok("Employee added successfully"); } else { return BadRequest("Employee is not added"); } } [HttpPost] [Route("[action]")] public IActionResult UpdateEmployee(Employee employee) { if (!ModelState.IsValid) { return BadRequest("Employee Model is Invalide"); } if (_employeeRepository.UpdateEmployee(employee)) { return Ok("Employee updated successfully"); } else { return BadRequest("Employee is not updated"); } } [HttpPost] [Route("[action]")] public IActionResult DeleteEmployee([FromQuery] int id) { Employee employee = _employeeRepository.GetEmployeeById(id); if (employee == null) { return BadRequest("Employee is not found"); } if (_employeeRepository.DeleteEmployee(employee)) { return Ok("Employee deleted successfully"); } else { return BadRequest("Employee is not updated"); } } }
Register the Employee Repository class & interface in the Program.cs file
builder.Services.AddScoped<IEmployeeRepository, EmployeeRepository>();
Now, create a unit test project using the NUnit framework. (Right Click on the solution -> Add -> New Project -> Nunit Test Project).
Install the following Nuget package
Microsoft.EntityFrameworkCore
Moq
We will create a Mock for DbContext & DbSet.
In GetMock generic static method for TContext and TData. It will return the DbContext Mock object
public class DbContextMock { public static TContext GetMock < TData, TContext > (List < TData > lstData, Expression < Func < TContext, DbSet < TData >>> dbSetSelectionExpression) where TData: class where TContext: DbContext { IQueryable < TData > lstDataQueryable = lstData.AsQueryable(); Mock < DbSet < TData >> dbSetMock = new Mock < DbSet < TData >> (); Mock < TContext > dbContext = new Mock < TContext > (); dbSetMock.As < IQueryable < TData >> ().Setup(s => s.Provider).Returns(lstDataQueryable.Provider); dbSetMock.As < IQueryable < TData >> ().Setup(s => s.Expression).Returns(lstDataQueryable.Expression); dbSetMock.As < IQueryable < TData >> ().Setup(s => s.ElementType).Returns(lstDataQueryable.ElementType); dbSetMock.As < IQueryable < TData >> ().Setup(s => s.GetEnumerator()).Returns(() => lstDataQueryable.GetEnumerator()); dbSetMock.Setup(x => x.Add(It.IsAny < TData > ())).Callback < TData > (lstData.Add); dbSetMock.Setup(x => x.AddRange(It.IsAny < IEnumerable < TData >> ())).Callback < IEnumerable < TData >> (lstData.AddRange); dbSetMock.Setup(x => x.Remove(It.IsAny < TData > ())).Callback < TData > (t => lstData.Remove(t)); dbSetMock.Setup(x => x.RemoveRange(It.IsAny < IEnumerable < TData >> ())).Callback < IEnumerable < TData >> (ts => { foreach(var t in ts) { lstData.Remove(t); } }); dbContext.Setup(dbSetSelectionExpression).Returns(dbSetMock.Object); return dbContext.Object; } }
List<TData> lstData - Mock data for DbSet. In our example, we are mocking the employee data.
Expression<Func<TContext, DbSet<TData>>> dbSetSelectionExpression - DbSet as an Expression.
Mock<DbSet> dbSetMock = new Mock<DbSet>() - Mocking the DbSet
Mock dbContext = new Mock() - Mocking the DbContext
Setup the Providers, Expression, ElementType and Enumerator.
Setup the CRUD methods for DbSet with Mocking data.
Create a mock object for the IEmployeeRepository interface.
public class IEmployeeRepositoryMock { public static IEmployeeRepository GetMock() { List < Employee > lstUser = GenerateTestData(); DataContext dbContextMock = DbContextMock.GetMock < Employee, DataContext > (lstUser, x => x.M_Employee); return new EmployeeRepository(dbContextMock); } private static List < Employee > GenerateTestData() { List < Employee > lstUser = new(); Random rand = new Random(); for (int index = 1; index <= 10; index++) { lstUser.Add(new Employee { Id = index, Name = "User-" + index, Age = rand.Next(1, 100) }); } return lstUser; } }
We are creating mock data for the Employee DbSet.
Creating the DbContext mock object using the DbContextMock.GetMock method. Here we are passing the Mock Employee Data and Employee DbSet.
Returning the object of EmployeeRepository class.
Creating EmployeeRepository Test class. In this class, we are going the test all the methods of IEmployeeRepository interface.
public class EmployeeRepositoryTest { private IEmployeeRepository employeeRepository; [SetUp] public void SetUp() { employeeRepository = IEmployeeRepositoryMock.GetMock(); } [Test] public void GetEmployees() { //Arrange //Act IList < Employee > lstData = employeeRepository.GetEmployees(); //Assert Assert.Multiple(() => { Assert.That(lstData, Is.Not.Null); Assert.That(lstData.Count, Is.GreaterThan(0)); }); } [Test] public void GetEmployeeById() { //Arrange int id = 1; //Act Employee data = employeeRepository.GetEmployeeById(id); //Assert Assert.Multiple(() => { Assert.That(data, Is.Not.Null); Assert.That(data.Id, Is.EqualTo(id)); }); } [Test] public void AddEmployee() { //Arrange Employee employee = new Employee() { Id = 100, Name = "New User", Age = 10 }; //Act bool data = employeeRepository.AddEmployee(employee); Employee expectedData = employeeRepository.GetEmployeeById(employee.Id); //Assert Assert.Multiple(() => { Assert.That(data, Is.True); Assert.That(expectedData, Is.Not.Null); Assert.That(expectedData.Id, Is.EqualTo(expectedData.Id)); }); } [Test] public void UpdateEmployee() { //Arrange int id = 2; Employee actualData = employeeRepository.GetEmployeeById(id); actualData.Name = "Update User"; actualData.Age = 1500; //Act bool data = employeeRepository.UpdateEmployee(actualData); Employee expectedData = employeeRepository.GetEmployeeById(actualData.Id); //Assert Assert.Multiple(() => { Assert.That(data, Is.True); Assert.That(expectedData, Is.Not.Null); Assert.That(expectedData, Is.EqualTo(actualData)); }); } [Test] public void DeleteEmployee() { //Arrange int id = 2; Employee actualData = employeeRepository.GetEmployeeById(id); //Act bool data = employeeRepository.DeleteEmployee(actualData); Employee expectedData = employeeRepository.GetEmployeeById(actualData.Id); //Assert Assert.Multiple(() => { Assert.That(data, Is.True); Assert.That(expectedData, Is.Null); }); } }
- In the SetUp method, we are getting the mock object for the IEmployeeRepository interface.
Goto to Test Explorer, Click on Run Test button.
Mocking DbContext and DbSet in a .NET 6 Web API using Moq brings numerous advantages to unit testing. By eliminating the need to connect to a live database, this approach significantly accelerates test execution, simplifies test setup, and allows for testing various scenarios and edge cases.
Remember, unit testing is a critical part of the software development process, and by mastering the art of mocking DbContext and DbSet, you can enhance the reliability, maintainability, and performance of your .NET 6 Web API. Happy mocking and testing!