Svenmarim
Svenmarim

Reputation: 3725

MOQ SetupGet dbContext with constructor parameters

Situation

Here I am, trying to write some unit tests for my GroupService with the use of MOQ.

To create an instance of my GroupService, I mocked 4 interfaces that needed to be passed through the constructor. Now on one of the mocks (IGroupRepository) a property called Context is called and my idea was to SetupGet this property and just simply return a fake list of GroupUser. But I keep getting errors, whatever I try.

Code

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);
    }
}

In the GroupService this line is called:

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

And the GroupRepository and EntityRepository look like this:

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; }
}

And last but not least, the AppDbContext and 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;
    }
}

Error

The error that I am getting is inside the SqlDbContext on the 1st line inside the constructor and says the following:

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.'

What am I doing wrong?

Upvotes: 1

Views: 2588

Answers (1)

rgvlee
rgvlee

Reputation: 3193

When you mock an implementation it creates the object using the constructor matching the parameters provided; it runs that code. Additionally, anything not able to be mocked (not virtual or abstract) will run as is. In this case, you're passing in DbContextOptions and you haven't specified a provider, and something needs that.

This can be an opinionated topic, however to solve your problem there are a number of ways you could do it:

  1. Add a parameterless constructor to your DbContext for testing. I wouldn't recommend this as I follow the mantra of not changing your SUT for a test.
  2. Use an in-memory provider; 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. Use an existing library such as EntityFrameworkCore.Testing (disclaimer, I am the author) which will extend in-memory providers to address their limitations.

Upvotes: 2

Related Questions