使用 Moq 和 xUnit 模拟 EF Core 数据库上下文?
Mocking EF Core Database Context using Moq and xUnit?
当我使用 DI 向控制器提供我的数据库上下文时,我想知道如何使用 Moq 来模拟我的 EF Core DbContext
,如下所示:
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();
}
}
这里 AppDbContext
是我的 EF 核心数据库上下文。
我想为 Create
操作编写测试用例。我试过下面的代码:
[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();
}
这里的主要问题是我做不到:
var mockRepo = new Mock<AppDbContext>();
我想遵循这种方法,因为我已经添加了内存数据库。
请注意,我知道还有另一种方法可以使用存储库模式进行测试。这可以通过将创建操作更改为:
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();
}
}
其中接口IRegisterRepository
和RegisterRepository.cs
代码为:
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();
}
}
和将其添加为服务的 startup.cs 代码是:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
services.AddScoped<IRegisterRepository, RegisterRepository>();
services.AddControllersWithViews();
}
我不想遵循这种方法(存储库模式)。我想直接伪造数据库上下文,因为我想直接在创建操作中插入记录,因为控制器正在其构造函数中获取数据库上下文对象。
那么如何在此测试方法中使用 moq 编写假数据库上下文?
有几个非常有用的库确实支持您正在寻找的东西。
这是我最喜欢的两个:
EntityFrameworkCore3Mock
唯一的先决条件是您必须将 DbSet
定义为 virtual
。
public class AppDbContext: DbContext
{
public virtual DbSet<Entity> Entities { get; set; }
}
那么模拟就这么简单:
var initialEntities = new[]
{
new Entity { ... },
new Entity { ... },
};
var dbContextMock = new DbContextMock<AppDbContext>(DummyOptions);
var usersDbSetMock = dbContextMock.CreateDbSetMock(x => x.Entities, initialEntities);
EntityFrameworkCore.Testing
这里唯一的区别是如何用数据初始化 table:
var initialEntities = new[]
{
new Entity { ... },
new Entity { ... },
};
var dbContextMock = Create.MockedDbContextFor<AppDbContext>();
dbContextMock.Set<Entity>().AddRange(initialEntities);
dbContextMock.SaveChanges();
不要模拟 DbContext,因为模拟 dbContext 的测试不会为开发人员提供高质量的反馈。
模拟的 DbContext 将仅验证某些方法被调用,这将使代码维护(重构)变成一场噩梦。
改为使用内存中提供程序或具有“内存中”功能的 Sqlite 提供程序。
当我使用 DI 向控制器提供我的数据库上下文时,我想知道如何使用 Moq 来模拟我的 EF Core DbContext
,如下所示:
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();
}
}
这里 AppDbContext
是我的 EF 核心数据库上下文。
我想为 Create
操作编写测试用例。我试过下面的代码:
[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();
}
这里的主要问题是我做不到:
var mockRepo = new Mock<AppDbContext>();
我想遵循这种方法,因为我已经添加了内存数据库。
请注意,我知道还有另一种方法可以使用存储库模式进行测试。这可以通过将创建操作更改为:
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();
}
}
其中接口IRegisterRepository
和RegisterRepository.cs
代码为:
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();
}
}
和将其添加为服务的 startup.cs 代码是:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
services.AddScoped<IRegisterRepository, RegisterRepository>();
services.AddControllersWithViews();
}
我不想遵循这种方法(存储库模式)。我想直接伪造数据库上下文,因为我想直接在创建操作中插入记录,因为控制器正在其构造函数中获取数据库上下文对象。
那么如何在此测试方法中使用 moq 编写假数据库上下文?
有几个非常有用的库确实支持您正在寻找的东西。
这是我最喜欢的两个:
EntityFrameworkCore3Mock
唯一的先决条件是您必须将 DbSet
定义为 virtual
。
public class AppDbContext: DbContext
{
public virtual DbSet<Entity> Entities { get; set; }
}
那么模拟就这么简单:
var initialEntities = new[]
{
new Entity { ... },
new Entity { ... },
};
var dbContextMock = new DbContextMock<AppDbContext>(DummyOptions);
var usersDbSetMock = dbContextMock.CreateDbSetMock(x => x.Entities, initialEntities);
EntityFrameworkCore.Testing
这里唯一的区别是如何用数据初始化 table:
var initialEntities = new[]
{
new Entity { ... },
new Entity { ... },
};
var dbContextMock = Create.MockedDbContextFor<AppDbContext>();
dbContextMock.Set<Entity>().AddRange(initialEntities);
dbContextMock.SaveChanges();
不要模拟 DbContext,因为模拟 dbContext 的测试不会为开发人员提供高质量的反馈。
模拟的 DbContext 将仅验证某些方法被调用,这将使代码维护(重构)变成一场噩梦。
改为使用内存中提供程序或具有“内存中”功能的 Sqlite 提供程序。