在与 WebApplicationFactory 的集成测试中重写数据库提供程序

问题描述 投票:0回答:1

遵循官方 MS 集成测试 .Net Core.

我能够完成集成测试的第一部分,其中我没有覆盖我正在测试的应用程序的启动类(即我使用的 Web 应用程序工厂没有覆盖任何服务)。

我想覆盖数据库设置以使用内存数据库进行集成测试。我遇到的问题是配置继续尝试使用 sql server

services.AddHangfire()

如何在集成测试中仅覆盖上述特定项目?我只想覆盖

AddHangfire
设置,而不是
services.AddScoped<ISendEmail, SendEmail>().
任何帮助将不胜感激。

使用自定义 Web 应用程序工厂测试类

 public class HomeControllerShouldCustomFactory : IClassFixture<CustomWebApplicationFactory<Startup>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<Startup> _factory;

        public HomeControllerShouldCustomFactory(CustomWebApplicationFactory<Startup> factory)
        {
            _factory = factory;
            _client = factory.CreateClient();
        }

        [Fact]
        public async Task IndexRendersCorrectTitle()
        {
            var response = await _client.GetAsync("/Home/Index");

            response.EnsureSuccessStatusCode();

            var responseString = await response.Content.ReadAsStringAsync();

            Assert.Contains("Send Email", responseString);
        }
}

自定义 Web 应用程序工厂

public class CustomWebApplicationFactory<TStartup>: WebApplicationFactory<SendGridExample.Startup>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                // Create a new service provider.
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();

                var inMemory = GlobalConfiguration.Configuration.UseMemoryStorage();
                services.AddHangfire(x => x.UseStorage(inMemory));

                // Build the service provider.
                var sp = services.BuildServiceProvider();

            });
        }
    }

我正在测试的应用程序中的startup.cs

public IConfiguration 配置 { get; } 公共 IHostingEnvironment 环境 { get; }

public void ConfigureServices(IServiceCollection services)
{
    services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    services.AddScoped<ISendEmail, SendEmail>();
}


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseHangfireServer();
    app.UseHangfireDashboard();
    RecurringJob.AddOrUpdate<ISendEmail>((email) => email.SendReminder(), Cron.Daily);
    app.UseMvc();

更新

我在其他仅使用实体框架的示例项目中没有看到这个问题。我有一个简单的应用程序,带有使用 SQL Server 的应用程序数据库上下文。在我的测试类中,我用内存数据库覆盖它,一切正常。我不明白为什么它可以在我的示例应用程序中工作,但不能在我的主应用程序中工作。这与 HangFire 的工作原理有关吗?

在我的测试应用程序(下面的示例代码)中,我可以删除我的 sql 数据库,运行我的测试,并且测试通过,因为应用程序数据库上下文不会查找 sql server 实例,而是使用内存数据库。在我的应用程序中,HangFire 服务不断尝试使用 sql server 数据库(如果我删除数据库并尝试使用内存数据库进行测试 - 它会失败,因为它找不到尝试连接的实例) 。当两个项目使用相似的路径时,为什么两个项目的工作方式会有如此巨大的差异?

我运行了集成测试的调试器,它调用上面的家庭控制器上的索引方法(使用 CustomWebApplicationFactory)。当我初始化测试服务器时,它会通过我的启动类,该类在下面的配置服务中调用:

services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice"))); 

之后,Configure 方法尝试调用以下语句:

app.UseHangfireServer();

此时测试失败,因为找不到数据库。该数据库托管在 Azure 上,因此我尝试将其替换为内存服务器以进行某些集成测试。我采取的方法不正确吗?


我的示例应用程序正在运行

我的示例应用程序中的应用程序数据库上下文

public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Message> Messages { get; set; }

        public async  Task<List<Message>> GetMessagesAsync()
        {
            return await Messages
                .OrderBy(message => message.Text)
                .AsNoTracking()
                .ToListAsync();
        }

        public void Initialize()
        {
            Messages.AddRange(GetSeedingMessages());
            SaveChanges();
        }

        public static List<Message> GetSeedingMessages()
        {
            return new List<Message>()
            {
                new Message(){ Text = "You're standing on my scarf." },
                new Message(){ Text = "Would you like a jelly baby?" },
                new Message(){ Text = "To the rational mind, nothing is inexplicable; only unexplained." }
            };
        }
    }

我的示例应用程序中的Startup.cs

services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

CustomWebApplicationFactory - 在我的单元测试项目中

public class CustomWebApplicationFactory<TStartup>
     : WebApplicationFactory<Startup>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                // Create a new service provider.
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();

                // Add a database context (ApplicationDbContext) using an in-memory 
                // database for testing.
                services.AddDbContext<ApplicationDbContext>(options =>
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                    options.UseInternalServiceProvider(serviceProvider);
                });

                // Build the service provider.
                var sp = services.BuildServiceProvider();

            });
        }
    }

我的单元测试项目中的单元测试

public class UnitTest1 : IClassFixture<CustomWebApplicationFactory<Startup>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<Startup> _factory;

        public UnitTest1(CustomWebApplicationFactory<Startup> factory)
        {
            _factory = factory;
            _client = factory.CreateClient();
        }


        [Fact]
        public async System.Threading.Tasks.Task Test1Async()
        {
            var response = await _client.GetAsync("/");

            //response.EnsureSuccessStatusCode();

            var responseString = await response.Content.ReadAsStringAsync();

            Assert.Contains("Home", responseString);
        }


更新2

我想我找到了尝试覆盖集成测试类中所有配置的替代方法。由于覆盖 HangFire 比覆盖 ApplicationDBContext 要复杂得多,我想出了以下方法:

Startup.cs

    if (Environment.IsDevelopment())
    {
        var inMemory = GlobalConfiguration.Configuration.UseMemoryStorage();
        services.AddHangfire(x => x.UseStorage(inMemory));
    }
    else
    {
        services.AddHangfire(x => x.UseSqlServerStorage(Configuration["DBConnection"]));
       
    }

然后在我的 CustomWebApplicationBuilder 中,我重写环境类型以进行测试:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<SendGridExample.Startup>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.UseEnvironment("Development"); //change to Production for alternate test
            builder.ConfigureServices(services =>
            {
                // Create a new service provider.
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();
           });
        }

    }

通过这种方法,我不需要担心必须执行额外的逻辑来满足hangfire 对活动数据库的检查。它有效,但我并不 100% 相信它是最好的方法,因为我在我的生产启动类中引入了分支。

c# asp.net-core
1个回答
4
投票

您需要检查两种不同的情况。

  1. 按班级创建工作
    BackgroundJob
  2. 通过界面创建作业
    IBackgroundJobClient

对于第一个选项,您无法将

SqlServerStorage
替换为
MemoryStorage

对于

UseSqlServerStorage
,它将按
JobStorage
重置
SqlServerStorage

        public static IGlobalConfiguration<SqlServerStorage> UseSqlServerStorage(
        [NotNull] this IGlobalConfiguration configuration,
        [NotNull] string nameOrConnectionString)
    {
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));
        if (nameOrConnectionString == null) throw new ArgumentNullException(nameof(nameOrConnectionString));

        var storage = new SqlServerStorage(nameOrConnectionString);
        return configuration.UseStorage(storage);
    }

UseStorage

    public static class GlobalConfigurationExtensions
{
    public static IGlobalConfiguration<TStorage> UseStorage<TStorage>(
        [NotNull] this IGlobalConfiguration configuration,
        [NotNull] TStorage storage)
        where TStorage : JobStorage
    {
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));
        if (storage == null) throw new ArgumentNullException(nameof(storage));

        return configuration.Use(storage, x => JobStorage.Current = x);
    }

这意味着,无论您在

CustomWebApplicationFactory
中设置什么,
UseSqlServerStorage
都会用
BackgroundJob
重置
SqlServerStorage

对于第二个选项,可以将

IBackgroundJobClient
替换为
MemoryStorage
by

    public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            services.AddSingleton<JobStorage>(x =>
            {
                return GlobalConfiguration.Configuration.UseMemoryStorage();
            });
        });
    }
}

总之,我建议您注册

IBackgroundJobClient
并尝试第二个选项来实现您的要求。

更新1

对于DB is not available,无法通过配置依赖注入来解决。这个错误是由调用

services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
引起的。

要解决此错误,您需要覆盖

Startup.cs
中的此代码。

尝试以下步骤:

  • Startup
    更改为以下:

    public class Startup
    {
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    
    public IConfiguration Configuration { get; }
    
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        //Rest Code
    
        ConfigureHangfire(services);
    }
    
    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        //Rest Code
        app.UseHangfireServer();
        RecurringJob.AddOrUpdate(() => Console.WriteLine("RecurringJob!"), Cron.Minutely);
    
    }
    
    protected virtual void ConfigureHangfire(IServiceCollection services)
    {
        services.AddHangfire(config =>
          config.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"))
        );
    }
    }
    
  • 在测试项目中创建

    StartupTest

    public class StartupTest : Startup
    {
    public StartupTest(IConfiguration configuration) :base(configuration)
    {
    
    }
    protected override void ConfigureHangfire(IServiceCollection services)
    {
        services.AddHangfire(x => x.UseMemoryStorage());
    }
    }
    
  • CustomWebApplicationFactory

    public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint: class
    {
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return WebHost.CreateDefaultBuilder(null)
            .UseStartup<TEntryPoint>();
    }
    }
    
  • 测试

    public class HangfireStorageStartupTest : IClassFixture<CustomWebApplicationFactory<StartupTest>>
    {
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<StartupTest> _factory;
    
    public HangfireStorageStartupTest(CustomWebApplicationFactory<StartupTest> factory)
    {
    
        _factory = factory;
        _client = factory.CreateClient();
    }
    }
    
© www.soinside.com 2019 - 2024. All rights reserved.