Reputation: 1
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
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