1
votes

I am seeding my in-memory data for my unit tests. I can add a user via UserManager, but the following call to add a test user to the admin role:

await _userManager.AddToRoleAsync(profile, "admin");

results in throwing the following error:

Cannot create a DbSet for 'IdentityRole' because this type is not included in the model for the context.

I am able to add roles via the RoleManager in my unit tests. I can add a user to a role in the application (not a test).

ApplicationRole as follows:

//
/// <summary>
/// Custom Application Role, inherits from IdentityRole
/// </summary>
public class ApplicationRole : IdentityRole
{
    //
    /// <summary>
    /// No parameter constructror, let base IdentityRole handle it.
    /// </summary>
    public ApplicationRole() : base() { }
    //
    /// <summary>
    /// 1 parameter constructror, let base IdentityRole handle it.
    /// </summary>
    /// <param name="name">just passed to base constructor</param>
    public ApplicationRole(string name) : base(name) { }
    //
    /// <summary>
    /// 2 parameter constructror, not passed to base constructor
    /// </summary>
    /// <param name="id"></param>
    /// <param name="name"></param>
    /// <remarks>Please use this constructor</remarks>
    public ApplicationRole(string id, string name) : base(name)
    {
        this.Id = id;
    }
    //
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
    //
}

My startup.cs as follows:

public class ApplicationDbContext :
    IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserClaim<string>, ApplicationUserRole, IdentityUserLogin<string>, IdentityRoleClaim<string>, IdentityUserToken<string>>, IDb_Context
{
    private object _options;
    //
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
        _options = options;
    }
    //
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //
        modelBuilder.Entity<ApplicationUser>((item) =>
        {
            item.Property(u => u.Id).HasMaxLength(128);
            item.HasMany(u => u.UserServers).WithOne(u => u.User)
                .HasForeignKey(u => u.Id).OnDelete(DeleteBehavior.Cascade);
            item.HasOne(u => u.Company).WithMany(c => c.Users)
                .HasForeignKey(u => u.CompanyId).OnDelete(DeleteBehavior.Restrict);
            // Each User can have many UserLogins
            item.HasMany(e => e.Logins).WithOne()
                .HasForeignKey(ul => ul.UserId).IsRequired();
            // Each User can have many UserTokens
            item.HasMany(e => e.Tokens).WithOne()
                .HasForeignKey(ut => ut.UserId).IsRequired();
        });
        //
        modelBuilder.Entity<ApplicationRole>((item) =>
        {
            item.Property(u => u.Id).HasMaxLength(128);
            item.HasKey(u => u.Id);
            item.HasMany(e => e.UserRoles).WithOne(e => e.Role)
                .HasForeignKey(ur => ur.RoleId).IsRequired();
        });
        //
        modelBuilder.Entity<ApplicationUserRole>((item) =>
        {
            item.Property(ur => ur.UserId).HasMaxLength(128);
            item.Property(ur => ur.RoleId).HasMaxLength(128);
            item.HasKey(ur => new { ur.UserId, ur.RoleId });
            item.HasOne(ur => ur.Role).WithMany(r => r.UserRoles)
                .HasForeignKey(ur => ur.RoleId).IsRequired();
            item.HasOne(ur => ur.User).WithMany(r => r.UserRoles)
                .HasForeignKey(ur => ur.UserId).IsRequired();
        });
        //
        ...
    }
    ...
    //
}

I create the context in a static helper as follows:

public static ApplicationDbContext GetInMemoryApplicationDBContext()
{
    ApplicationDbContext _context = null;
    // Use in memory application DB context
    var _optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>()
        .UseInMemoryDatabase("db_context" + Guid.NewGuid().ToString())
        .EnableSensitiveDataLogging();
    _context = new ApplicationDbContext(_optionsBuilder.Options);
    return _context;
}

How can I have my in-memory database add a user to the role?

1

1 Answers

1
votes

Well it has been a long road and the answer is I used the following blog post: Unit Testing ASP.NET Core Identity, with source on GitHub. I used the basic ideas, but I created a class that I inherit from, because I am using MS-Test.

using System;
using Microsoft.AspNetCore.Identity;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
//
using NSG.WebSrv;
using NSG.WebSrv.Domain.Entities;
//
namespace NSG.WebSrv_Tests
{
    public class UnitTestFixture : IDisposable
    {
        //
        static private SqliteConnection sqliteConnection;
        static public ApplicationDbContext db_context;
        static public UserManager<ApplicationUser> userManager;
        static public RoleManager<ApplicationRole> roleManager;
        //
        public UnitTestFixture()
        {
        }
        //
        public void UnitTestSetup()
        {
            // Build service colection to create identity UserManager and RoleManager. 
            IServiceCollection serviceCollection = new ServiceCollection();
            // Add ASP.NET Core Identity database in memory.
            sqliteConnection = new SqliteConnection("DataSource=:memory:");
            serviceCollection.AddDbContext<ApplicationDbContext>(
                options => options.UseSqlite(sqliteConnection)
            );
            //
            db_context = serviceCollection.BuildServiceProvider()
                .GetService<ApplicationDbContext>();
            db_context.Database.OpenConnection();
            db_context.Database.EnsureCreated();
            // Add Identity using in memory database to create UserManager and RoleManager.
            serviceCollection.AddApplicationIdentity();
            // Get UserManager and RoleManager.
            userManager = serviceCollection.BuildServiceProvider().GetService<UserManager<ApplicationUser>>();
            roleManager = serviceCollection.BuildServiceProvider().GetService<RoleManager<ApplicationRole>>();
        }
        //
        /// <summary>
        /// Cleanup resources
        /// </summary>
        public void Dispose()
        {
            if(db_context != null)
            {
                db_context.Database.EnsureDeleted();
                db_context.Dispose();
            }
            sqliteConnection.Close();
        }
    }
}

The key is Identity is setup as a static extension method that can be used by both the application and the test:

//
/// <summary>
/// Startup Extensions
/// </summary>
public static class StartupExtensions
{
    public static IServiceCollection AddApplicationIdentity(this IServiceCollection services)
    {
        services.AddIdentity<ApplicationUser, ApplicationRole>(
            options => {
                options.Stores.MaxLengthForKeys = 128;
                options.Password.RequireDigit = true;
                options.Password.RequiredLength = 8;
                options.Password.RequireLowercase = true;
                options.Password.RequireUppercase = true;
                options.Password.RequireNonAlphanumeric = true;
            })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddUserManager<UserManager<ApplicationUser>>()
            .AddRoleManager<RoleManager<ApplicationRole>>()
            .AddDefaultUI(UIFramework.Bootstrap4)
            .AddDefaultTokenProviders();
        return services;
    }
}