Переопределение поставщика базы данных в интеграционном тесте с WebApplicationFactory - PullRequest
0 голосов
/ 14 октября 2018

Я следую официальной документации MS для интеграционного тестирования .Net Core (https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.1).

Мне удалось выполнить первую часть интеграционного теста, где я не переопределял класс запускаприложения, которое я тестирую (т. е. я использовал фабрику веб-приложений, которая не переопределяла никакие сервисы).

Я хочу переопределить настройку базы данных, чтобы использовать базу данных в памяти для интеграционного теста. Проблема Iя сталкиваюсь с тем, что конфигурация продолжает пытаться использовать сервер SQL для services.AddHangfire().

Как переопределить только выше определенного элемента в моем интеграционном тесте? Я хочу переопределить только настройку AddHangfire ине services.AddScoped<ISendEmail, SendEmail>(). Любая помощь приветствуется.

Тестовый класс с фабрикой пользовательских веб-приложений

 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);
        }
}

Фабрика пользовательских веб-приложений

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 Configuration {get;} public IHostingОкружающая среда Окружающая среда}

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.В моем тестовом классе я перезаписываю его базой данных в памяти, и все работает.Я затрудняюсь понять, почему это будет работать в моем примере приложения, но не будет работать в моем основном приложении.Это как-то связано с тем, как работает HangFire?

В моем тестовом приложении (пример кода ниже) я могу удалить свою базу данных sql, запустить мой тест, и тест проходит, потому что контекст БД приложения не просматриваетсядля экземпляра сервера SQL, но использует базу данных в памяти.В моем приложении служба HangFire продолжает пытаться использовать базу данных sql-сервера (если я удаляю базу данных и пытаюсь использовать базу данных в памяти для теста - происходит сбой, потому что не удается найти экземпляр, к которому она пытается подключиться),Почему такая большая разница в том, как работают два проекта, когда для обоих используется одинаковый путь?

Я пробежал отладчик для моего интеграционного теста, который вызывает метод index на контроллере home выше (используяCustomWebApplicationFactory).Когда я инициализирую тестовый сервер, он проходит через мой класс запуска, который вызывает ниже в ConfigureServices:

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);
        }

enter image description here


Обновление 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% убежден, что это лучший подход, так как я внедряю ветвление в своем классе запуска производства.

1 Ответ

0 голосов
/ 16 октября 2018

Есть два разных сценария, которые вы должны проверить.

  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 на

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

В заключение, я предлагаю вам зарегистрировать IBackgroundJobClient и попробовать второй вариант, чтобы удовлетворить ваши требования.

Update1

Для БД недоступна , ее невозможно разрешить путем настройки внедрения зависимостей.Эта ошибка вызвана вызовом 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();
    }
    }
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...