.NET 6 Web API Mocking DbContext & DbSet using Moq

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:

  1. Create an ASP.NET Core Web API application.

    Select ASP.NET Core Web API

  2. Here, We are using an SQLite database. So we have installed the Entity framework package for SQLite. Install the following Nuget packages.

  3. 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.)

  4. In appsettings.json, Update the connection string.

     {
        "ConnectionStrings": "Data Source=.\\Database\\User.db;"
     }
    
  5. 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;
                }
            }
      
  6. 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");
                 }
             }
         }
    
  7. Register the Employee Repository class & interface in the Program.cs file

     builder.Services.AddScoped<IEmployeeRepository, EmployeeRepository>();
    
  8. Now, create a unit test project using the NUnit framework. (Right Click on the solution -> Add -> New Project -> Nunit Test Project).

  9. Install the following Nuget package

    • Microsoft.EntityFrameworkCore

    • Moq

  10. 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.

  11. 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.

  12. 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.
  13. 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!

0
Subscribe to my newsletter

Read articles from Ashok Kuduva Ananthan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ashok Kuduva Ananthan
Ashok Kuduva Ananthan