EntityFrameworkCore.AutoFixture
EntityFrameworkCore.AutoFixture copied to clipboard
Unable to create DB Context with SQL Server provider
Hi Andrei
Do you have (or could you prepare) any samples showing how to use (or extend) DbContextCustomization to connect to a SQL Server database, for integration test purposes please?
I have created a customization as follows:
public class TestDbContextCustomization : DbContextCustomization
{
public override void Customize(IFixture fixture)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(@"appsettings.json", false, false)
.AddEnvironmentVariables()
.Build();
var connectionString = config["ConnectionStrings:CIServerIntegrationTests"];
Configure = o => o.UseSqlServer(connectionString);
OmitDbSets = false;
OnCreate = OnCreateAction.None;
base.Customize(fixture);
}
}
And use the customization as follows:
var fixture = new Fixture().Customize(new TestDbContextCustomization());
var context = fixture.Create<TestDbContext>();
var sale = await context.Sales.FirstOrDefaultAsync();
The db context has a normal constructor:
public TestDbContext(DbContextOptions<TestDbContext> options)
: base(options)
{
}
However it errors with "No database provider has been configured for this DbContext". Am I missing a step to properly configure the provider? A breakpoint on the UseSqlServer method call is never hit.
Thank you
I've got something working, having changed the customization to use the following code:
public override void Customize(IFixture fixture)
{
base.Customize(fixture);
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(@"appsettings.json", false, false)
.AddEnvironmentVariables()
.Build();
var connectionString = config["ConnectionStrings:CIServerIntegrationTests"];
OmitDbSets = false;
OnCreate = OnCreateAction.None;
fixture.Customize<SqlConnection>(c => c.Without(s => s.RetryLogicProvider));
fixture.Register<SqlConnection>(() => new SqlConnection(connectionString));
fixture.Customizations.Add(new FilteringSpecimenBuilder(
new OptionsBuilderConfigurator(
new SqlServerOptionsBuilder(new MethodInvoker(new ModestConstructorQuery()), this.ConfigureProvider),
this.Configure),
new ExactTypeSpecification(typeof(DbContextOptionsBuilder<>))));
}
And created a SqlServerOptionsBuilder (implementing ISpecimenBuilder), containing the following code:
public Action<SqlServerDbContextOptionsBuilder>? ConfigureProvider { get; set; }
/// <inheritdoc />
public object Create(object request, ISpecimenContext context)
{
Check.NotNull(context, nameof(context));
var result = this.Builder.Create(request, context);
var connection = context.Create<SqlConnection>();
if (result is not DbContextOptionsBuilder optionsBuilder)
return new NoSpecimen();
return optionsBuilder.UseSqlServer(connection, this.ConfigureProvider);
}
Notice that in the customization I register the SqlConnection so it forces the use of the constructor with the connection string value passed in explicitly. I did try the approach that SqliteCustomization uses instead of the Register call:
fixture.Customizations.Add(new FilteringSpecimenBuilder(
new FixedBuilder(connectionString),
new AndRequestSpecification(
new ParameterSpecification(typeof(string), ConnectionStringParameter),
new ParameterMemberSpecification(
new DeclaringTypeSpecification(typeof(SqlConnection))))));
but when the SqlConnection was created in the options builder, it always errored due to the connection string being empty. Not sure what I was doing wrong, but the Register call looks ok as a workaround..
Hello @danielgreen ,
Not sure if this is still relevant for you but if you copied the rest of the sqlite customization, the reason the constructor parameter customization wasn't working might be because the SqlConnection has three constructors as opposed to SqliteConnection that has only two. This means that the selected constructor was the one with an argument for SqlCredential which would have been created by AutoFixure with random data.
Also the DbContextCustomization was intended to be used as a base type for provider customizations that would extend the behavior. Just setting the Configure property doesn't do anything. The property was intended to be applied after the provider has been configured, for example to setup the logging.
I will prepare a sample project to showcase a possible set up for SQLServer, but if you're not very familiar with AutoFixture you might find it difficult to adapt for your needs.
Hi @aivascu you might be right. Although the error when constructing SqlConnection seems to relate to the connectionString argument, not the credential.
System.ArgumentException: Format of the initialization string does not conform to specification starting at index 0.
No credential argument is required but unsure if I can force AutoFixture to use the constructor with one parameter. In any case, the workaround of using the Register call is ok. Thanks for your help