Интеграционные тесты в ASP.NET Core
 
                        
                    Тестирование - это очень важный аспект в написании кода. Интеграционные тесты, это "контроль качества" нашего приложения. Очень важно понимать как писать интаграционные тесты правильно. Сегодня мы рассмотрим как "запустить" колесо интеграционных тестов в ASP.NET Core для REST API.
Для начала создадим наш проект:

Как тип проекта выберем API

.NET нам создаст готовый класс, который будет прогнозировать погоду.

Давайте запустим и протестируем его в постмене:

Теперь приступим непосредственно к созданию тестов. Создадим проект с тестами:

Перед тем как писать сами тесты, нам нужно внести несколько изменений в Startup класс.  В моем конкретном примере. я не использую базу данных. Но покажу вам как создать inmemory database для тестирования.
Первым делом установим Entity Framework пакеты с помощью команды:
install-package Microsoft.EntityFrameworkCore
install-package Microsoft.EntityFrameworkCore.InMemory
install-package Microsoft.EntityFrameworkCore.SqlServerТеперь модифицируем ConfigureServices для работы в тестах:
  public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            AddDb(services);
            ConfigureDependencies(services);
        }
public virtual void ConfigureDependencies(IServiceCollection services)
        {
        }
    private void AddDb(IServiceCollection services)
        {
            if (_currentEnvironment.IsEnvironment("Testing"))
            {
                services.AddDbContextPool<ApplicationDbContext>(options =>
                    options.UseInMemoryDatabase("TestingDB"));
            }
            else
            {
                services.AddDbContextPool<ApplicationDbContext>(options =>
                    options.UseSqlServer(
                        Configuration.GetConnectionString("DefaultConnection")));
            }
        }Полностью наш Startup класс будет выглядеть так:
    public class Startup
    {
        private readonly IWebHostEnvironment _currentEnvironment;
        public IConfiguration Configuration { get; }
        public Startup(IConfiguration configuration, IWebHostEnvironment currentEnvironment)
        {
            Configuration = configuration;
            _currentEnvironment = currentEnvironment;
        }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            AddDb(services);
            ConfigureDependencies(services);
        }
        public virtual void ConfigureDependencies(IServiceCollection services)
        {
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
        private void AddDb(IServiceCollection services)
        {
            if (_currentEnvironment.IsEnvironment("Testing"))
            {
                services.AddDbContextPool<ApplicationDbContext>(options =>
                    options.UseInMemoryDatabase("TestingDB"));
            }
            else
            {
                services.AddDbContextPool<ApplicationDbContext>(options =>
                    options.UseSqlServer(
                        Configuration.GetConnectionString("DefaultConnection")));
            }
        }
    }
   public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }Далее возвращаемся к проекту тестов который мы создали и добавим туда TestStartup
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Web;
namespace Tests
{
    public class TestStartup : Startup
    {
        public TestStartup(IConfiguration configuration, IWebHostEnvironment currentEnvironment) : base(configuration, currentEnvironment)
        {
        }
        public override void ConfigureDependencies(IServiceCollection services)
        {
            base.ConfigureDependencies(services);
        }
    }
}
ConfigureDependencies сейчас у меня не переопределяет никаких сервисов, но в реальном приложении, там бы мы переопределяли реальные сервисы нашими моками.
Для начала поставим тест хост для нашего проекта с тестами
install-package Microsoft.AspNetCore.TestHost -v 3.00Теперь добавим базовую фикстуру для XUnit тестов:
 public class BaseTestServerFixture
    {
        public TestServer TestServer { get; }
        public ApplicationDbContext DbContext { get; }
        public HttpClient Client { get; }
        public BaseTestServerFixture()
        {
            var builder = new WebHostBuilder()
                .UseEnvironment("Testing")
                .UseStartup<Startup>();
            TestServer = new TestServer(builder);
            Client = TestServer.CreateClient();
            DbContext = TestServer.Host.Services.GetService<ApplicationDbContext>();
        }
        public void Dispose()
        {
            Client.Dispose();
            TestServer.Dispose();
        }
    }Далее пишем сам тест для нашего "прогнозатора" погоды:
 public class WeatherForecastControllerTests : IClassFixture<BaseTestServerFixture>
    {
        private readonly BaseTestServerFixture _fixture;
        public WeatherForecastControllerTests(BaseTestServerFixture fixture)
        {
            _fixture = fixture;
        }
        [Fact]
        public async Task Get_ShouldReturnListResult()
        {
            // Arrange
            var response = await _fixture.Client.GetAsync("/WeatherForecast/");
            response.EnsureSuccessStatusCode();
            var models = JsonConvert.DeserializeObject<IEnumerable<WeatherForecast>>(await response.Content.ReadAsStringAsync());
            // Assert
            Assert.NotEmpty(models);
        }
    }Отмечу, что если вы не можете правильно выбрать именование для тестов, у нас была статья на эту тему.
Исходный код проекта можно найти тут.
