简体   繁体   中英

How to test method in XUnit that needs UserManager, but uses in-memory database

I'm using ASP.NET Core 3.1 and XUnit for my unit tests.

I built a database context factory class that instantiates an in-memory version of my database:

public static class DbContextFactory
{
    public static ApplicationDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;

        var modelBuilder = new ModelBuilder(new ConventionSet());

        var dbContext = new ApplicationDbContext(options);

        var onModelCreatingMethod = dbContext.GetType().GetMethod("OnModelCreating",
            BindingFlags.Instance | BindingFlags.NonPublic);

        onModelCreatingMethod.Invoke(dbContext,
            new object[] { modelBuilder });

        return dbContext;
    }
}

This is the current test class I'm trying to use:

public class AdminServiceTests
{
    public ApplicationDbContext context { get; set; }
    public IAdminService adminService { get; set; }

    public AdminServiceTests()
    {
        this.context = DbContextFactory.CreateDbContext();
        this.adminService = new AdminService(userManager, context);
    }

    [Fact]
    public async Task DeleteUserShouldDeleteUser()
    {
        // What to do ???
    }
}

In order for me to test my admin service, I need to provide a user manager. It should be linked with the database I currently have created.

How can I make that happen?

You're making a common mistake of testing the framework. All your test needs to do is ensure that AdminService.DeleteUser calls UserManager.DeleteAsync . Whether or not that spirals down into actually removing the user from the database is 1) not a concern of the service and 2) an implementation detail of both ASP.NET Core Identity and EF Core, both of which have their own extensive test suites to ensure that happens.

As such, you can just use a library like Moq to create a mock of UserManager<TUser> and then do something like:

userManagerMock.Verify(x => x.DeleteAsync(user), Times.Once());

It's worth mentioning here that this also serves to point out a bit of a flaw in this kind of design. You have a dependency on ASP.NET Core Identity whether or not you put an AdminService wrapper around that. Unless your service is doing something special outside of just proxying to UserManager here (eg coordinating multiple actions, like maybe deleting the user triggers a notification or something), then your service is pointless, and you should just use UserManager directly. Developers make this kind of mistake constantly; abstraction for the sake of abstraction only hurts your code. It adds additional maintenance concerns, testing concerns , and obscures what the code is actually doing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM