如何在需要 UserManager 但使用内存数据库的 XUnit 中测试方法
How to test method in XUnit that needs UserManager, but uses in-memory database
我正在使用 ASP.NET Core 3.1 和 XUnit 进行单元测试。
我构建了一个数据库上下文工厂 class 来实例化我的数据库的内存版本:
public static class DbContextFactory
{
public static ApplicationDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var modelBuilder = new ModelBuilder(new ConventionSet());
var dbContext = new ApplicationDbContext(options);
var onModelCreatingMethod = dbContext.GetType().GetMethod("OnModelCreating",
BindingFlags.Instance | BindingFlags.NonPublic);
onModelCreatingMethod.Invoke(dbContext,
new object[] { modelBuilder });
return dbContext;
}
}
这是当前测试class我正在尝试使用:
public class AdminServiceTests
{
public ApplicationDbContext context { get; set; }
public IAdminService adminService { get; set; }
public AdminServiceTests()
{
this.context = DbContextFactory.CreateDbContext();
this.adminService = new AdminService(userManager, context);
}
[Fact]
public async Task DeleteUserShouldDeleteUser()
{
// What to do ???
}
}
为了测试我的管理服务,我需要提供一个用户管理器。它应该与我当前创建的数据库链接。
我怎样才能做到这一点?
您在测试框架时犯了一个常见错误。您所有的测试需要做的就是确保 AdminService.DeleteUser
调用 UserManager.DeleteAsync
。这是否会导致实际从数据库中删除用户是 1) 不是服务的问题,2) ASP.NET Core Identity 和 EF Core 的实现细节,两者都有自己的广泛测试套件以确保发生这种情况。
因此,您可以只使用像 Moq 这样的库来创建 UserManager<TUser>
的模拟,然后执行以下操作:
userManagerMock.Verify(x => x.DeleteAsync(user), Times.Once());
这里值得一提的是,这也算是指出了这种设计的一点瑕疵。你对 ASP.NET Core Identity 有依赖性,无论你是否在其周围放置 AdminService
包装器。除非你的服务在代理 UserManager
之外做一些特殊的事情(例如协调多个动作,比如删除用户触发通知或其他东西),那么你的服务是没有意义的,你应该只使用 UserManager
直接。开发人员经常犯这种错误;为了抽象而抽象只会伤害你的代码。它增加了额外的维护问题,测试问题,并掩盖了代码的实际作用。
我正在使用 ASP.NET Core 3.1 和 XUnit 进行单元测试。
我构建了一个数据库上下文工厂 class 来实例化我的数据库的内存版本:
public static class DbContextFactory
{
public static ApplicationDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var modelBuilder = new ModelBuilder(new ConventionSet());
var dbContext = new ApplicationDbContext(options);
var onModelCreatingMethod = dbContext.GetType().GetMethod("OnModelCreating",
BindingFlags.Instance | BindingFlags.NonPublic);
onModelCreatingMethod.Invoke(dbContext,
new object[] { modelBuilder });
return dbContext;
}
}
这是当前测试class我正在尝试使用:
public class AdminServiceTests
{
public ApplicationDbContext context { get; set; }
public IAdminService adminService { get; set; }
public AdminServiceTests()
{
this.context = DbContextFactory.CreateDbContext();
this.adminService = new AdminService(userManager, context);
}
[Fact]
public async Task DeleteUserShouldDeleteUser()
{
// What to do ???
}
}
为了测试我的管理服务,我需要提供一个用户管理器。它应该与我当前创建的数据库链接。
我怎样才能做到这一点?
您在测试框架时犯了一个常见错误。您所有的测试需要做的就是确保 AdminService.DeleteUser
调用 UserManager.DeleteAsync
。这是否会导致实际从数据库中删除用户是 1) 不是服务的问题,2) ASP.NET Core Identity 和 EF Core 的实现细节,两者都有自己的广泛测试套件以确保发生这种情况。
因此,您可以只使用像 Moq 这样的库来创建 UserManager<TUser>
的模拟,然后执行以下操作:
userManagerMock.Verify(x => x.DeleteAsync(user), Times.Once());
这里值得一提的是,这也算是指出了这种设计的一点瑕疵。你对 ASP.NET Core Identity 有依赖性,无论你是否在其周围放置 AdminService
包装器。除非你的服务在代理 UserManager
之外做一些特殊的事情(例如协调多个动作,比如删除用户触发通知或其他东西),那么你的服务是没有意义的,你应该只使用 UserManager
直接。开发人员经常犯这种错误;为了抽象而抽象只会伤害你的代码。它增加了额外的维护问题,测试问题,并掩盖了代码的实际作用。