最小起订量设置使用构造函数参数获取 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));
GroupRepository
和 EntityRepository
看起来像这样:
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; }
}
最后但同样重要的是,AppDbContext
和 SqlDbContext
:
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 并且您没有指定提供程序,而某些东西需要它。
这可能是一个自以为是的话题,但是要解决您的问题,您可以采用多种方法:
- 将无参数构造函数添加到您的 DbContext 以进行测试。我不推荐这样做,因为我遵循不更改 SUT 进行测试的原则。
- 使用 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.
- 使用现有的库,例如 EntityFrameworkCore.Testing(免责声明,我是作者),它将扩展 in-memory 个提供商以解决它们的局限性。
情况
我在这里,尝试使用最小起订量为我的 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));
GroupRepository
和 EntityRepository
看起来像这样:
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; }
}
最后但同样重要的是,AppDbContext
和 SqlDbContext
:
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 并且您没有指定提供程序,而某些东西需要它。
这可能是一个自以为是的话题,但是要解决您的问题,您可以采用多种方法:
- 将无参数构造函数添加到您的 DbContext 以进行测试。我不推荐这样做,因为我遵循不更改 SUT 进行测试的原则。
- 使用 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.
- 使用现有的库,例如 EntityFrameworkCore.Testing(免责声明,我是作者),它将扩展 in-memory 个提供商以解决它们的局限性。