最小起订量设置使用构造函数参数获取 dbContext

MOQ SetupGet dbContext with constructor parameters

情况

我在这里,尝试使用最小起订量为我的 GroupService 编写一些单元测试。

为了创建我的 GroupService 的实例,我模拟了 4 个需要通过构造函数传递的接口。现在在其中一个模拟 (IGroupRepository) 上调用了一个名为 Context 的 属性,我的想法是 SetupGet 这个 属性 并且只是 return GroupUser 的虚假列表。但是无论我尝试什么,我总是会出错。

代码

public class GroupServiceTests
{
    private readonly GroupService _groupService;
    private readonly Mock<AppDbContext> _dbContext;
    private readonly Mock<IGroupRepository> _groupRepository;
    private readonly Mock<IComponentService> _componentService;
    private readonly Mock<IUserContextService> _userContextService;
    private readonly Mock<IModelEntityMapper<Group, Core.DbContexts.Entities.Group>> _mapper;

    public GroupServiceTests()
    {
        var groupUsersMock = CreateDbSetMock(GetFakeListOfGroupUsers());
        _dbContext = new Mock<AppDbContext>(new DbContextOptions<AppDbContext>());
        _dbContext.SetupGet(x => x.GroupUser).Returns(groupUsersMock.Object);

        _groupRepository = new Mock<IGroupRepository>();
        _groupRepository.SetupGet(repo => repo.Context).Returns(_dbContext.Object);

        _componentService = new Mock<IComponentService>();
        _userContextService = new Mock<IUserContextService>();
        _mapper = new Mock<IModelEntityMapper<Group, Core.DbContexts.Entities.Group>>();

        _groupService = new GroupService(_groupRepository.Object, _componentService.Object, _userContextService.Object, _mapper.Object);
    }
}

GroupService 中,这一行被称为:

// _repository reffers to IGroupRepository
userIdsForContextReset.AddRange(_repository.Context.GroupUser.Where(x => groupIds.Contains(x.GroupId)).Select(x => x.UserId));

GroupRepositoryEntityRepository 看起来像这样:

public interface IGroupRepository : IEntityRepository<AppDbContext, Group>
{
    List<GroupPermission> GetInheritedGroupPermissions(int groupId);
}

public class GroupRepository : EntityRepository<AppDbContext, Group>, IGroupRepository
{
    public GroupRepository(AppDbContext dbContext) : base(dbContext)
    {
    }

    public List<GroupPermission> GetInheritedGroupPermissions(int groupId)
    {
        // Removed for brevity
    }
}
public class EntityRepository<TDbContext, TEntity> : EntityRepository<TDbContext, TEntity, int>, IEntityRepository<TDbContext, TEntity>
     where TDbContext : DbContext
     where TEntity : class, IEntity<int>
{
    public EntityRepository(TDbContext dbContext) : base(dbContext)
    {
    }
}
public class EntityRepository<TDbContext, TEntity, TId> : IEntityRepository<TDbContext, TEntity, TId>
     where TDbContext : DbContext
     where TEntity : class, IEntity<TId>
     where TId : IComparable
{
    public EntityRepository(TDbContext context)
    {
        Context = context;
    }

    public TDbContext Context { get; }
}

最后但同样重要的是,AppDbContextSqlDbContext:

public class AppDbContext : Shared.DbContexts.SqlDbContext
{
    public virtual DbSet<GroupUser> GroupUser { get; set; }

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }
}
public class SqlDbContext : DbContext
{
    public SqlDbContext(DbContextOptions options) : base(options)
    {
        ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        ChangeTracker.StateChanged += ChangeTracker_StateChanged;
    }
}

错误

我得到的错误是在构造函数内第一行的 SqlDbContext 内,并说明如下:

System.InvalidOperationException: 'No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.'

我做错了什么?

当您模拟一个实现时,它会使用与提供的参数匹配的构造函数创建对象;它 运行 就是那个代码。 此外,任何无法模拟的东西(不是虚拟的或抽象的)都将 运行 保持原样。在这种情况下,您正在传递 DbContextOptions 并且您没有指定提供程序,而某些东西需要它。

这可能是一个自以为是的话题,但是要解决您的问题,您可以采用多种方法:

  1. 将无参数构造函数添加到您的 DbContext 以进行测试。我不推荐这样做,因为我遵循不更改 SUT 进行测试的原则。
  2. 使用 in-memory 供应商; EF Core In-Memory Database Provider or SQLite EF Core Database Provider are two that I have used. They do have limitations but for the OP usage would probably be fine and addresses Microsofts notes about how you shouldn't mock the DbContext.
  3. 使用现有的库,例如 EntityFrameworkCore.Testing(免责声明,我是作者),它将扩展 in-memory 个提供商以解决它们的局限性。