Home > Software design >  How to unit test my EF repository method using Moq?
How to unit test my EF repository method using Moq?

Time:06-27

I'm attempting to write a unit test for my repository class for its Create method that uses DbContext and the Add method.

My idea was to get a count of the number of existing records. Call the Create method. Get the new count of records and check that its incremented by 1.

However, when running the unit test, it errors when calling the Add method with the following error:

{"Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DbContext'."}

I'm trying to understand why is this and how to overcome this?

public class MyDatabaseContext : DbContext
{
    public MyDatabaseContext(DbContextOptions<MyDatabaseContext> options) : base(options)
    {
    }

    public DbSet<Record> Records { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Record>();
    }
}

public class Repository : IRepository
{
    private readonly MyDatabaseContext _dbContext;

    public Repository(MyDatabaseContext dbContext)
    {
        _dbContext = dbContext;
    }

    public Record Create(Record record)
    {
        try
        {
            using (_dbContext)
            {
                var response = _dbContext.Records.Add(record); //erroring line
                _dbContext.SaveChanges();
                return response.Entity;
            }
        }
        catch (Exception ex)
        {
            return null;
        }
    }
    
    public IEnumerable<Record> GetAll()
    {
        try
        {
            using (_dbContext)
            {
                return _dbContext.Records.ToList();
            }
        }
        catch (Exception ex)
        {
            return null;
        }
    }
}

public interface IRepository
{
    Record Create(Record record);
    IEnumerable<Record> GetAll();
}

Startup.cs:

services.AddDbContext<MyDatabaseContext>(opt => opt.UseInMemoryDatabase("memoryDb"));
services.AddScoped<IRepository, Repository>();

Unit test:

[TestMethod]
public async Task Create_Successfully()
{
    var repository = new Repository(await GetDbContext());
    var existingRecords = repository.GetAll();

    repository.Create(new Record());
    var newRecords = repository.GetAll();

    Assert.AreEqual(3, existingRecords.Count());
    Assert.AreEqual(4, newRecords.Count());
}

private async Task<DbContext> GetDbContext()
{
    var options = new DbContextOptionsBuilder<DbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;

    var context = new DbContext(options);
    context.Database.EnsureCreated();

    if (await context.Records.CountAsync() <= 0)
    {
        for (int i = 1; i <= 3; i  )
        {
            context.Records.Add(new Records());
            await context.SaveChangesAsync();
        }
    }

    return context;
}

CodePudding user response:

You need to remove the using statements in the GetAll and Create methods:

public Record Create(Record record)
{
    try
    {
        using (_dbContext)
        {
            var response = _dbContext.Records.Add(record); //erroring line
            _dbContext.SaveChanges();
            return response.Entity;
        }
    }
    catch (Exception ex)
    {
        return null;
    }
}

To:

public Record Create(Record record)
{
    try
    {
        var response = _dbContext.Records.Add(record); //erroring line
        _dbContext.SaveChanges();
        return response.Entity;
    }
    catch (Exception ex)
    {
        return null;
    }
}

You don't need to worry about disposing the service as the conatiner will do that for you in production. In your tests you can do this if you want to clean things up:

[TestMethod]
public async Task Create_Successfully()
{
    using (var context = await GetDbContext())
    {
        var repository = new Repository(context);
        var existingRecords = repository.GetAll();

        repository.Create(new Record());
        var newRecords = repository.GetAll();

        Assert.AreEqual(3, existingRecords.Count());
        Assert.AreEqual(4, newRecords.Count());
    }
}
  • Related