简体   繁体   中英

Mocking EF Core Database Context using Moq and xUnit?

I want to know how to use Moq for mocking my EF Core's DbContext when I am using DI to provide my Database context to the controller as shown below:

public class RegisterController : Controller
{
    private AppDbContext context;
    public RegisterController(AppDbContext appDbContext)
    {
        context = appDbContext;
    }

    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Create(Register register)
    {
        if (ModelState.IsValid)
        {
            context.Add(register);
            await context.SaveChangesAsync();

            return RedirectToAction("Read");
        }
        else
            return View();
    }
}

Here AppDbContext is my database context for the EF core.

I want to write test case for the Create action. I have tried the below code:

[Fact]
public async Task Test_Create_POST_ValidModelState()
{
    // Arrange
    var r = new Register()
    {
        Id = 4,
        Name = "Test Four",
        Age = 59
    };

    var mockRepo = new Mock<AppDbContext>();
    mockRepo.Setup(repo => repo.CreateAsync(It.IsAny<Register>()))
        .Returns(Task.CompletedTask)
        .Verifiable();
    var controller = new RegisterController(mockRepo.Object);

    // Act
    var result = await controller.Create(r);

    // Assert
    var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
    Assert.Null(redirectToActionResult.ControllerName);
    Assert.Equal("Read", redirectToActionResult.ActionName);
    mockRepo.Verify();
}

The main problem here is that I cannot do:

var mockRepo = new Mock<AppDbContext>();

I want to follow this approach as I have already added in-memory database.

Note that I know there is a another way to test with repository pattern. Which can be done by changing the create action as:

public class RegisterController : Controller
{
    private IRegisterRepository context;
    public RegisterController(IRegisterRepository appDbContext)
    { 
        context = appDbContext;
    }

    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Create(Register register)
    {
        if (ModelState.IsValid)
        {
            await context.CreateAsync(register);
            return RedirectToAction("Read");
        }
        else
            return View();
    }
}

Where the interface IRegisterRepository and RegisterRepository.cs codes are:

public interface IRegisterRepository
{     
    Task CreateAsync(Register register);
}

public class RegisterRepository : IRegisterRepository
{
    private readonly AppDbContext context;

    public RegisterRepository(AppDbContext dbContext)
    {
        context = dbContext;
    }

    public Task CreateAsync(Register register)
    {
        context.Register.Add(register);
        return context.SaveChangesAsync();
    }
}

and the startup.cs code which add it as a service is:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<AppDbContext>(optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
    services.AddScoped<IRegisterRepository, RegisterRepository>();
    services.AddControllersWithViews();
}

I do not want to follow this approach (the repository pattern). I want to fake database context directly because I want to directly do the insertion of the record in the create action as the controller is getting database context object in it's constructor.

So how to write fake database context using moq in this test methods here?

Don't mock DbContext , because tests with mocking dbContext will not provide good quality feedback for developer.

Mocked DbContext will verify only that some method are called, which will convert code maintenance (refactoring) into a nightmare.

Instead use In-Memory provider or Sqlite provider with "in-memory" feature.

EF Core In-Memory Database Provider

Using SQLite to test an EF Core application

Alternative approach is to test against actual database - such tests will provide more confidence that application works correctly in "production" environment.

There are several really useful libraries which does support what you are looking for.
Here are two of my favourite ones:

EntityFrameworkCore3Mock

Github repo

The only prerequisite is that you have to define your DbSet as virtual .

public class AppDbContext: DbContext
{
    public virtual DbSet<Entity> Entities { get; set; }
}

Then the mocking would be this easy:

var initialEntities = new[]
{
    new Entity { ... },
    new Entity { ... },
};

var dbContextMock = new DbContextMock<AppDbContext>(DummyOptions);
var usersDbSetMock = dbContextMock.CreateDbSetMock(x => x.Entities, initialEntities);

EntityFrameworkCore.Testing

Github repo

The only difference here is how you initialize the table with data:

var initialEntities = new[]
{
    new Entity { ... },
    new Entity { ... },
};


var dbContextMock = Create.MockedDbContextFor<AppDbContext>();
dbContextMock.Set<Entity>().AddRange(initialEntities);
dbContextMock.SaveChanges();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM