Nana E.
Nana E.

Reputation: 1

DbContext.Set<TEntity> throws null exception in xUnit tests

I have implemented an abstract generic repository that defines a set of methods for performing CRUD operations. I am trying to test them with the help of the Unit of Work pattern in a derived class. I'm using Moq as a testing framework and MockQueryable to enable Async operations like FirstOrDefaultAsync, AddAsync, SaveChangesAsync etc. I am experiencing an error testing the Add method. Here is a dummy project I created to show how things are set up.

DbContext Class

public class MyDBContextClass : DbContext, IDbContext
{
   public MyDBContextClass(DbContextOptions options) : base(options) { }

   public DbSet<Students> Students { get; set; }
}

Abstract class

public abstract class MyGenericRepository<TEntity> where TEntity : class
{
   private readonly IDbContext context;

   public MyMyGenericRepository(IDbContext context){
        this.context = context;
   }

   public virtual async Task<int> Add(TEntity Entity)
   {
       await context.Set<TEntity>().AddAsync(entity);  // throws a NullReferenceException during test
       await context.SaveChangesAsync();      
   }
}

Unit of Work Class

public class StudentUnitOfWork : MyGenericRepository<Student>
{

   public class StudentUnitOfWork(IDBContext context) : base(context)
   {

   }
} 

The test class

public class StudentUnitOfWorkTest
{
   [Fact]
   public async Task Add()
   {
      var students = new List<Student>
      {
        new Student{ Id = 1, Name = "John Doe"},
        new Student{ Id = 2, Name = "Jane Doe"}
      }

      var mockSet = students.AsQueryable().BuildMockDbSet();
      var mockContext = new Mock<IDbContext>();
      mockContext.Setup(_ => _.Students).Returns(mockSet.Object);
      var sut = new StudentUnitOfWork(mockContext.Object);

      var newStudentObj = new Student
      {
        Name = "Justin Doe" 
      }

      await sut.Add(newStudentObj); // throws a NullReferenceException
      
      mockSet.Verify(_ => _.AddAsync(It.IsAny<Student>(), It.IsAny<CancellationToken>()), Times.Once());
      mockContext.Verify(_ => _.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once());

   }
}

Overriding and injecting the base Add method also fails

   public override async Task Add(Student student)
   {
       await base.Add(category); // Still throws a NullReferenceException
   } 

The test passes when I override the Implementation of Add in the Unit of Work Class and Set the actual dbSet in this case Students.

public class StudentUnitOfWork : MyGenericRepository<Student>
{
   public override async Task Add(Student student)
   {
       await context.Students.AddAsync(student); // Works
       await context.SaveChangesAsync();
   }
} 

Am I missing anything or DbContext.Set only works in production and not tests.

Upvotes: 0

Views: 1662

Answers (1)

desertech
desertech

Reputation: 1029

You are not configuring the Set method in IDbContext mock to return anything in your setup code, hence it returns null (default) and an exception is thrown when calling AddAsync.

Update:

Here's the line with error, where you call Set method:

await context.Set<TEntity>().AddAsync(entity);

And Here's your setup code in test, where you skip handling that same Set method:

var mockSet = students.AsQueryable().BuildMockDbSet();
var mockContext = new Mock<IDbContext>();
mockContext.Setup(_ => _.Students).Returns(mockSet.Object);

And since you are not mocking Set method, it returns null when test runs. You obviously cannot call AddAsync method on null, therefore NullReferenceException is thrown.

You should solve it by adding something like that:

mockContext.Setup(_ => _.Set<Student>()).Returns(<DbSet mock object>);

On the other hand,

await context.Students.AddAsync(student);

works, because you have mocked Students property

Upvotes: 3

Related Questions