I have a relatively simple ASP.NET Core 6 application that I want to cover by integration tests.
I followed several tutorials, but my first test fails on
System.InvalidOperationException: The entry point exited without ever building an IHost.
The real problem is
System.InvalidOperationException: Services for database providers 'Microsoft.Ent
ityFrameworkCore.Sqlite', 'Microsoft.EntityFrameworkCore.SqlServer' have been re
gistered in the service provider. Only a single database provider can be registe
red in a service provider. If possible, ensure that Entity Framework is managing
its service provider by removing the call to 'UseInternalServiceProvider'. Othe
rwise, consider conditionally registering the database provider, or maintaining
one service provider per database provider.
I can't figure out what is wrong. I tried several approaches, none of them work.
using Serilog;
namespace TaskScheduler.Service;
public class Program
{
private static async Task Main(string[] args)
{
Log.Logger = Logging.Log;
try
{
var host = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>())
.ConfigureServices()
.RegisterServices()
.ConfigureLogging()
.ConfigureSettings()
.UseSerilog()
.Build();
await host.RunAsync();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
await Log.CloseAndFlushAsync();
}
}
}
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
namespace TaskScheduler.Service.Extensions;
public static class ServicesRegistrationExtension
{
public static IHostBuilder RegisterServices(this IHostBuilder hostBuilder) =>
hostBuilder.ConfigureServices(services => _ = services
.AddHttpClient()
.AddMemoryCache()
.AddTransient<IJobDetailCreator, RestGetActivity>()
.AddTransient<IJobDetailCreator, RestPostActivity>()
.AddTransient(typeof(IInOutRepository<,,>), typeof(EfRepository<,,>))
.AddTransient(typeof(ICacheRepository<,>), typeof(CacheRepository<,>))
.AddTransient(typeof(IModelRepository<,>), typeof(ModelRepository<,>))
.AddTransient<IAuditingRepository<ScheduleTaskModel>, ScheduleTaskAuditRepository>()
.AddTransient<IActivityObjectRepositoryFactory, ActivityObjectRepositoryFactory>()
.AddTransient<IActivityObjectRepository, ActivityObjectRepository<GetRestModel>>()
.AddTransient<IActivityObjectRepository, ActivityObjectRepository<PostRestModel>>()
.AddTransient(typeof(IActivityRepository<>), typeof(ActivityRepository<>))
.AddDbContext<TaskSchedulerContext>(
x => x
.UseSqlServer()
.UseQueryTrackingBehavior(
QueryTrackingBehavior.NoTrackingWithIdentityResolution)
.EnableThreadSafetyChecks()
.EnableDetailedErrors()
.EnableSensitiveDataLogging(),
ServiceLifetime.Scoped,
ServiceLifetime.Scoped)
.AddDbContext<CatalogContext>(
x => x
.UseSqlServer(),
ServiceLifetime.Scoped,
ServiceLifetime.Scoped)
.AddTransient<IQuartzJobFactory, QuartzJobFactory>()
.AddSingleton<ISchedulerFactory, StdSchedulerFactory>()
.AddTransient<IJobFactory, IntegrationJobFactory>()
.AddTransient<IServiceProvider>(s => services.BuildServiceProvider())
.AddTransient<IDateTimeUtility, DateTimeUtility>()
.AddQuartz(quartz => quartz.UseMicrosoftDependencyInjectionJobFactory())
.AddQuartzHostedService(options => options.WaitForJobsToComplete = true)
.AddHttpClient<CommonApiAuthenticationHandler>((provider, client) =>
client.BaseAddress = new Uri(
provider.GetOptionsValue<SemAuthenticationOptions>().CommonApiUrl)));
}
using Serilog;
namespace TaskScheduler.Service;
public class Startup
{
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
TaskSchedulerContext taskSchedulerContext)
{
if (env.IsDevelopment())
{
_ = app.UseDeveloperExceptionPage();
_ = taskSchedulerContext.Database.EnsureCreated();
}
else
{
_ = app.UseHttpsRedirection();
}
_ = app
.UseRouting()
.UseCors(cors => cors
.SetIsOriginAllowed(_ => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials())
.UseAuthorization()
.UseSwagger()
.UseSwaggerUI(c => c.SwaggerEndpoint(
$"/swagger/{Consts.SwaggerDocName}/swagger.json",
"TaskScheduler.Service.Api.V1"))
.UseSerilogRequestLogging()
.UseEndpoints(endpoints => endpoints
.MapControllers()
.RequireAuthorization(SecurityConsts.TokenAuthorizationPolicy));
}
}
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Data.Common;
using TaskScheduler.Service.DataLayer.Sql;
namespace TaskScheduler.Service.Integration.Tests;
public class TaskServiceFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder) =>
builder.ConfigureServices(InjectInMemoryDb);
private static void InjectInMemoryDb(IServiceCollection services)
{
_ = services.RemoveAll(typeof(DbContextOptions));
_ = services.RemoveAll(typeof(DbContextOptions<TaskSchedulerContext>));
_ = services.RemoveAll(typeof(TaskSchedulerContext));
_ = services.RemoveAll(typeof(DbContextOptions<CatalogContext>));
_ = services.RemoveAll(typeof(CatalogContext));
_ = services.AddSingleton<DbConnection>(_ =>
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
return connection;
});
_ = services.AddDbContext<TaskSchedulerContext>(UseSqLite)
.AddDbContext<CatalogContext>(UseSqLite);
using var scope = services.BuildServiceProvider().CreateScope();
var repositoryContext = scope.ServiceProvider.GetRequiredService<TaskSchedulerContext>();
_ = repositoryContext.Database.EnsureDeleted();
_ = repositoryContext.Database.EnsureCreated();
}
private static void UseSqLite(IServiceProvider container, DbContextOptionsBuilder options)
{
var connection = container.GetRequiredService<DbConnection>();
_ = options.UseSqlite(connection);
}
}
using FluentAssertions;
using FluentAssertions.Execution;
using System.Net;
using TaskScheduler.Service.DataLayer.Sql;
namespace TaskScheduler.Service.Integration.Tests;
public class TaskTests : IClassFixture<TaskServiceFactory>
{
private readonly HttpClient _client;
public TaskTests(TaskServiceFactory factory)
{
using var scope = factory.Services.CreateScope();
var repositoryContext =
scope.ServiceProvider.GetRequiredService<TaskSchedulerContext>();
_ = repositoryContext.Database.EnsureDeleted();
_ = repositoryContext.Database.EnsureCreated();
_client = factory.CreateDefaultClient();
}
[Fact]
public async Task GetAllScheduleTasksTest()
{
var result = await _client.GetAsync("schedule-tasks");
using var scope = new AssertionScope();
_ = result.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
The error indicates you've create a dbcontext with two database providers.
Since you've replaced DbContextOptions in IServiceCollection with these lines:
_ = services.RemoveAll(typeof(DbContextOptions<TaskSchedulerContext>));
_ = services.AddSingleton<DbConnection>(_ =>
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
return connection;
});
_ = services.AddDbContext<TaskSchedulerContext>(UseSqLite)
.AddDbContext<CatalogContext>(UseSqLite);
private static void UseSqLite(IServiceProvider container, DbContextOptionsBuilder options)
{
var connection = container.GetRequiredService<DbConnection>();
_ = options.UseSqlite(connection);
}
Check your dbcontext if you've configured other database providers(Microsoft.EntityFrameworkCore.SqlServer),I reproduced the error with the following steps:
services.AddDbContext<SomeContext>(op=>op.UseSqlServer(connectstring))
in dbcontext:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectstring);
}
it works well till now,but after I tried :
services.AddDbContext<SomeContext>(op=>op.UseSqlServer(connectstring))
builder.Services.RemoveAll(typeof(DbContextOptions<SomeContext>))
services.AddDbContext<SomeContext>(op=>op.InMemoryDatabase(dbname))
I got the same error
and fixed it with remove
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectstring);
}
or add a judgement:
if(!optionsBuilder.IsConfigured)
if you still get the error please check if you congiured the database provider any where else and provide the minimal codes that could reproduce the error