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?
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:
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.
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.
Use an existing library such as EntityFrameworkCore.Testing (disclaimer, I am the author) which will extend in-memory providers to address their limitations.
Related
I am trying to build my own implementation of IdentityPackage. In my project I have a class AddIdenitityServices. When trying to inject the class into the service collection I get the error CS0119. I am still learning about generics so when I searched up the error it did not make much sense to me.
Here is my injection code.
public static IServiceCollection AddIdentityServices<TUser,TDbContext>
(this IServiceCollection services,
IdentityDbOptions options
) where TDbContext : IdentityDbContext<TUser> where TUser : IdentityDbUser
{
services.AddScoped<IIdentityServiceManager<TUser>, IdentityServicesManager<TUser,TDbContext>();
return services;
}
Here is my Service manager code
public class IdentityServicesManager<TUser, TDbContext>
where TDbContext : IdentityDbContext<TUser> where TUser : IdentityDbUser,
IIdentityServiceManager<TUser>
{
private readonly TDbContext _context;
public IdentityServicesManager(TDbContext context)
{
_context = context ?? throw new NullReferenceException($"{nameof(context)} was null");
}
}
and finally my IdentityDbContext
public class IdentityDbContext<TUser>: DbContext where TUser : IdentityDbUser
{
public IdentityDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<TUser> Users { get; set; }
}
I did try making the Interface also contain the TDbContext. But it still failed. It requires the TUser due to a function requiring it.
I dd also try reading though Microsoft Docs on the issue but I am still not sure exactly what the issue will be
What I currently have:
I have a simple dictionary of type string, DbContext
I am registering my Dictionary like this
container.Register<IDictionary<string, DbContext>>(x => dbContexts).ReusedWithin(ReuseScope.Request);
and using the dictionary again by simply injecting it in my constructors. So I am always injecting the whole dictionary with every DbContext in it. Later I then get the right DbContext by using an identifier, stored in my usersession. So using a DbContext looks like this:
private readonly IDictionary<string, DbContext> _dbContexts;
public FooService(IDictionary<string, DbContext> dbContexts)
{
_dbContexts = dbContexts;
}
public void Bar()
{
var userSession = GetSession();
var data = _dbContexts[userSession.TargetConnectionIdentifier].Table.ToList();
}
What I want
I want to inject only one DbContext into my classes. Using the same property from my session as an identifier. I have seen that Funq offers methods to register instances with a name (e.g. RegisterAs<>()). But I am not quite sure how to use them correctly.
private readonly DbContext _dbContext;
public FooService(DbContext dbContext)
{
_dbContext = dbContext;
}
I want that Funq automatically resolves the right object for me, depending on what value a specific property from my session has.
If anybody knows an answer I would highly appreciate it.
The IOC doesn't have access to the runtime Request context so you couldn't do this within the IOC.
My approach would be to register a factory with the db contexts, e.g:
container.Register<IDbContexts>(c => new DbContexts(dbContexts));
Then you could have a base class that provides a helper to access the DbContext behind a helper, e.g:
public class ServiceBase : Service
{
public IDbContexts DbContexts { get; set; }
DbContext dbContext;
public DbContext DbContext => dbContext ?? (dbContext = DbContexts.Get(GetSession()));
}
Or you could use Extension methods if you don't want a base class, e.g:
public static class MyServiceExtensions
{
public static DbContext GetDbContext(this Service service)
{
var dbContexts = service.TryResolve<IDbContexts>();
return dbContexts.Get(service.GetSession());
}
}
Then lazily load it in your Service class like:
DbContext dbContext;
public DbContext DbContext => dbContext ?? (dbContext = this.GetDbContext());
My application has two databases: MainDbContext which inherits from DbContext and IdentifiedDbContext which inherits from MainDbContext.
MainDbContext uses base classes like EntityBase with Id, createdAt, updatedAt. IdentifiedDbContext uses IdentifiedEntityBase classes that inherit from EntityBase with properties CreatorId, OwnerId.
My application also has a Repository, and IdentifiedRepository which inherits from Repository.
Code looks like this:
public class BeehouseContext:DbContext
{
public BeehouseContext(DbContextOptions<BeehouseContext> options) : base(options)
{
// -- Construtor
}
protected BeehouseContext(DbContextOptions options):base(options)
{
// -- Construtor fix
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
#region EntityBase
modelBuilder.Ignore<Beehouse.Framework.Domain.EntityBase>();
#endregion
}
}
{
public BeehouseIdentityContext(DbContextOptions<BeehouseIdentityContext> options) : base(options)
{
// -- Construtor
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
#region EntityBase
modelBuilder.Ignore<Beehouse.Framework.Identity.Domain.IdentifiedEntity>();
#endregion
}
}
public class Repository<TEntity> : IRepository<TEntity> where TEntity : EntityBase
{
protected readonly BeehouseContext Context;
public Repository(BeehouseContext context)
{
Context = context;
}
private DbSet<TEntity> _entities;
protected virtual DbSet<TEntity> Entities
{
get
{
if (_entities is null)
{
_entities = Context.Set<TEntity>();
}
return _entities;
}
}
// Other methods
}
public class IdentifiedRepository<TEntity>:Repository<TEntity>, IIdentifiedRepository<TEntity> where TEntity:IdentifiedEntity
{
protected readonly string UserId; // as Dealer
protected readonly string CreatorId; // as Retailer
protected readonly string CreatorAlias;
public IdentifiedRepository(IHttpContextAccessor httpContextAccessor, BeehouseIdentityContext context):base(context)
{
if (httpContextAccessor.HttpContext.User.FindFirst("sub") == null) return;
CreatorId = httpContextAccessor.HttpContext.User.FindFirst("sub").Value;
CreatorAlias = httpContextAccessor.HttpContext.User.FindFirst(SubscriptionClaimTypes.ProfileName).Value;
UserId = httpContextAccessor.HttpContext.User.FindFirst(SubscriptionClaimTypes.DealerUserId).Value;
}
In dependency injection I can create an instance of IdentifiedRepository, but in some cases I need to instantiate the Repository only, but I can't. The following error is thrown:
InvalidOperationException: Unable to resolve service for type
'Microsoft.EntityFrameworkCore.DbContextOptions`1[Beehouse.Framework.Identity.Data.BeehouseIdentityContext]'
while attempting to activate
'Beehouse.Framework.Identity.Data.BeehouseIdentityContext'.
I've tried to edit constructors like:
BeehouseContext(DbContextOptions options) : base(options)
BeehouseIdentityContext(DbContextOptions options) : base(options)
But this doesn't work either.
My dependency injection looks like this and doesn't work:
// Database
services.AddDbContext<IdentityServerContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("connstring")));
services.AddTransient(typeof(BeehouseContext), typeof(BeehouseIdentityContext));
I also tried to add DbContext like this:
services.AddDbContext<IdentityServerContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("auxiliarc-local")));
services.AddDbContext<BeehouseContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("auxiliarc-local")));
But that fails too with the following error:
InvalidOperationException: Cannot create a DbSet for 'Plan' because
this type is not included in the model for the context.
Plan are in namespace and Project Identified, but inherits from EntityBase only.
Can somebody help-me? Thanks.
The injector is looking to create an instance of type BeehouseIdentityContext.
The constructor requires an object of type DbContextOptions. The injector cannot instantiate this type for the activation of BeehouseIdentityContext so it throws an error.
Either tell the injector how to create instances of type DbContextOptions or create a constructor that uses defaults and does not need this type.
I am still struggling to make good data access layer for asp mvc application and as always something is missing :)
I have created separate assembly for DAL and I am using repository pattern and ninject for IOC.
Problem is that now I don't know how to write custom methods (methods out of generic CRUD methods).
This is implementation:
Context class:
public class MainContext : IdentityDbContext<ApplicationUser, ApplicationRole, int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>, IMainContext
{
public MainContext()
: base("DefaultConnection")
{
}
...
public DbSet<Country> Countries { get; set; }
...
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
Repository:
public interface ICountryRepository : IGenericRepository<Country>...
Generic Repository:
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
private readonly IMainContext _context;
private readonly IDbSet<TEntity> _dbSet;
public GenericRepository(IMainContext context)
{
this._context = context;
this._dbSet = context.Set<TEntity>();
}
...
Generic Repository Interface:
public interface IGenericRepository<TEntity> where TEntity : class
{
IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "");
TEntity GetByID(object id);
void Insert(TEntity entity);
void Delete(object id);
void Delete(TEntity entityToDelete);
void Update(TEntity entityToUpdate);
}
And this is try to add custom method in repository class:
public virtual IEnumerable<Country> GetByLocation(float location)
{
var data = from c in _context...
return data;
}
But I don't have a context.
I don't know how to implement getting data now.
Should I inject it somehow or make instance by new keyword (but I guess this is wrong)
How to implement custom method now?
Your implementation of ICountryRepository should inherit the GenericRepository. The GenericRepository has a reference to the db context which you can use for your custom queries. For your Generic Repository constructor is will be much easier to take MainContext instead of an IMainContext which is ok if you keep injecting it down through you layers. With Ninject you will want to bind your MainContext like this:
kernel.Bind<MainContext>().ToSelf().InRequestScope();
and the rest of your interfaces to the concrete implementation. So each of your repositories will have the context through the GenericRepository which has a reference to your db context which you can make custom queries off of in the repository. If you had a service layer, inject the interface of the repository:
private readonly ICountryRepository _repository;
public SomeServie(ICountryRepository repository){
_repository = repository;
}
public void DoSomething(float locationId){
_repository.GetByLocation(locationId);
}
HERE IS THE OTHER CODE YOU NEED:
public class CountryRepository : GenericRepository<Country>, ICountryRepository
{
public CountryRepository(MainContext mainContext) : base(mainContext) { }
public IEnumerable<Country> GetByLocation(float location)
{
return this.Context.Countries.ToList();
}
.....
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
public MainContext Context { get; set; }
public IDbSet<TEntity> DbSet { get; set; }
public GenericRepository(MainContext context)
{
Context = context;
DbSet = context.Set<TEntity>();
}
.......
Also, your GetCountries does not need to be virtual.
The main changes here are that you need to pass the context through the country repository constructor to base, and the context and countries db set need to be public, even when inherited. See this article: Are private members inherited in C#?
i try to write Generic Repository in EF 4. But my codes is not looking good. Because ExpressionEntities is not GENERIC.
I want to convert this codes:
public class DataRepository<T> : IRepository<T> where T : class
{
private ExpressionsEntities _context;
public DataRepository()
{
}
public DataRepository(ExpressionsEntities context)
{
_context = context;
}
}
to the following:
public class DataRepository<T> : IRepository<T> where T : class
{
private GetGenericEntityCONTEXT _context;
public DataRepository()
{
}
public DataRepository(GetGenericEntityCONTEXT context)
{
_context = context;
}
}
because ExpressionsEntities not global my entities : ExpressionsEntities1, ExpressionsEntities 2, ExpressionsEntities 3 etx... i need to write get take entity for example:
public class DataRepository<T> : IRepository<T> where T : class
{
private Entity _context;
public DataRepository()
{
}
public DataRepository(Entity context)
{
_context = context;
}
public class Main
{
main()
{
new DataRepository(ExpressionEntities)
}
}
}
Not 100% sure what your asking - from what i can understand, you wan't a way to dynamically create the entity set based on T.
Well that's easy enough:
public class DataRepository<T> : IRepository<T> where T : class
{
private ObjectContext _ctx;
public DataRepository<T>(ObjectContext ctx)
{
this._ctx = ctx;
}
public IObjectSet<T> CurrentEntitySet<T>()
{
get
{
var entityName = _plularizer.Pluralize(typeof(T).Name);
string entitySetName = string.Format("{0}.{1}", EntityContainerName, entityName);
return _ctx.CreateObjectSet<T>(entitySetName );
}
}
}
Then your specific Repository could do this (for example):
public class AppleRepository : DataRepository<Apple>
{
public AppleRepository(IObjectContext ctx) : base(ctx) {}
public ICollection<Apple> FindApples(Func<Apple,bool> predicate)
{
return CurrentEntitySet.Where(predicate).ToList();
}
}
And when your creating your repository, pass through the object context - preferably by DI:
var repository = new AppleRepository(new ExpressionEntities()); // should be DI'ed
Basically, we're making use of Pluralization (the same code used by Entity Framework to pluralize entity set names), and CreateObjectSet<T>.
So if you created a DataRepository<Apple>, that would translate to an entity set name of Apples (which should match the entity set on your model), and we create an entity set based on that.
The EntityContainerName property is what's on your EDMX - you should pass this through the ctor (via DI preferably).
Does that answer your question?
I prefer to get the entityName like this.
EntityContainer container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
EntitySetBase entitySet = container.BaseEntitySets.Where(item => item.ElementType.Name.Equals(typeof(T).Name)).FirstOrDefault();
var entityName = entitySet.Name
This deals with some cases if you have some classes pluralized, and some classes not.