Reputation: 25
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
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