UnBroKeN
UnBroKeN

Reputation: 25

Setting up an Autofixture for In-memory DbContext

I am currently trying to use Autofixture to create a pre-defined fixture as an implementation of ICustomization for ApplicationDbContext using In-Memory provider.

public class ApplicationDbContextFixture : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var specimenFactory = new SpecimenFactory<ApplicationDbContext>(CreateDbContext);
        fixture.Customize<ApplicationDbContext>(
                composer =>
                    composer.FromFactory(specimenFactory)
                );
    }

    /// <summary>
    /// Private factory method to create a new instance of <see cref="ApplicationDbContext"/>
    /// </summary>
    private ApplicationDbContext CreateDbContext()
    {
        var dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
                    .UseInMemoryDatabase("SomeDatabaseName")
                    .Options;
        var dbContext = new ApplicationDbContext(dbContextOptions);        
        return dbContext;
    }
}

Then, I will apply that customization to my Fixture as follows:

    [Fact]
    public void TestAddUsersToEmptyDatabase()
    {
        // Arrange
        // Fixture for ApplicationDbContext
        var fixture = FixtureFactory.CreateFixture();
        var applicationDatabaseFixture = new ApplicationDbContextFixture();
        fixture.Customize(applicationDatabaseFixture);

        // Fixture for users
        var randomUser = fixture.Create<AppUser>();
        var normalUser = fixture.Create<AppUser>();
        var adminUser = fixture.Create<AppUser>();

        // Act & Assert
        // Run the test against one instance of the context
        // Use a clean instance of the context for each operation too
        using (var dbContext = fixture.Create<ApplicationDbContext>())
        {
            Assert.Empty(dbContext.Users);
            dbContext.Users.Add(randomUser);
            dbContext.SaveChanges();
        }

        using (var dbContext = fixture.Create<ApplicationDbContext>())
        {
            dbContext.Users.AddRange(normalUser, adminUser);
            dbContext.SaveChanges();
        }

        using (var dbContext = fixture.Create<ApplicationDbContext>())
        {
            Assert.NotEmpty(dbContext.Users);
            Assert.NotNull(dbContext.Users.SingleOrDefault(_ => _.Id == randomUser.Id));
            Assert.NotNull(dbContext.Users.SingleOrDefault(_ => _.Id == normalUser.Id));
            Assert.NotNull(dbContext.Users.SingleOrDefault(_ => _.Id == adminUser.Id));
        }
    }

FixtureFactory.CreateFixture implementation

    /// <summary>
    /// Factory method to declare a single <see cref="IFixture"/> for unit tests applications
    /// </summary>
    internal static class FixtureFactory
    {
        internal static IFixture CreateFixture()
        {
            var fixture = new Fixture().Customize(
                new AutoMoqCustomization { ConfigureMembers = true });

            return fixture;
        }
    }

Now in my unit test, asserting the Assert.Empty(dbContext.Users); will throw System.NotImplementedException : The method or operation is not implemented. because the DbSet<AppUser> Users generated from Autofixture is a DynamicProxy.

See image dbContext.Users as DynamicProxy

Oddly enough if I inspect the breakpoints from the factory method (ie. CreateDbContext()) called from the fixture.Create<ApplicationDbContext>(), the DbSet Users is of the expected type.

See image dbContext.Users as InternalDbSet

Optionally, I do aware that I can replace all the usage of dbContext.Users to dbContext.Set<User>() and that would make the unit test pass but the problem is that in the actual class, I am using the dbContext.Users for IQueryables and database operations, so I still need to stick with it if possible.

Hence, I would need help to know why does AutoFixture used my factory method to generate the instance for my ApplicationDbContext but all the DbSet<> properties inside it are mocked when resolved by the ISpecimenBuilder. Is there a way to remedy this?

I've post the similar question in their Github but it has been not active recently, so i also asked here.

Kindly please understand I only started to use Autofixture 2 days ago. So if there's something that I write wrong or there's a misconception in any Design Patterns, please kindly wrote a comment so that I can take it as a lesson.

Update 1: So i tried to use initialized a plain fixture without any AutoMoq customization (ie. fixture = new Fixture()) and this time it throws a AutoFixture.ObjectCreationExceptionWithPath exception, complaining that it is unable to resolve DbSet property within the ApplicationDbContext. At this point, I was thinking if anyone know how to use a Relay or ISpecimenBuilder to tell Autofixture to use/call/implement all DbSet<T> properties within the ApplicationDbContext with dbContext.Set<T> because that would work if I replace all usage of DbSets in my unit tests, but as I mentioned, all IQueryable are return from DbSets so i cannot simply just replace it in ApplicationDbContext.

Update 2: I remove and simplify the creation of ApplicationDbContext from my factory method CreateDbContext() since it will cause confusion from the code complexity.

Upvotes: 2

Views: 4501

Answers (1)

Andrei Ivascu
Andrei Ivascu

Reputation: 1274

It is hard to understand what you are trying to achieve from your post.

I think what you actually need, is to test your code that happens to use EntityFramework. If that's the case you might want to have a look at this library, I have created EntityFrameworkCore.AutoFixture. It uses the In-Memory database provider as well as SQLite in-memory provider.

Here's a sample of what the tests can look like.

public class InMemoryDataAttribute : AutoDataAttribute
{
    public InMemoryDataAttribute()
        : base(() => new Fixture()
            .Customize(new InMemoryCustomization()))
    {
    }
}
[Theory, InMemoryData]
public async Task SampleTest([Frozen] ApplicationDbContext context, IEnumerable<AppUser> users)
{
    // Arrange
    await context.Users.AddRangeAsync(users);
    await context.SaveChangesAsync();

    // Force EF Core to reload entities from the database
    context.Users.Clear();

    // Act
    var actual = context.Users.ToList();

    // Assert
    Assert.Equal(users, actual, new IdentiyUserComparer());
}

Have a look at the readme for some more code samples. If you have any questions drop me a message or open an issue on GitHub.

Upvotes: 1

Related Questions