Интеграционные тесты в ASP.NET Core

Интеграционные тесты в ASP.NET Core

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

Для начала создадим наш проект:

create project

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

.net core api

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

structure

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

test weather

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

test project

Перед тем как писать сами тесты, нам нужно внести несколько изменений в 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);
        }
    }

Отмечу, что если вы не можете правильно выбрать именование для тестов, у нас была статья на эту тему.

Исходный код проекта можно найти тут.

0 67 07.02.2020 08:26

Комментарии:

Пожалуйста авторизируйтесь, чтобы получить возможность оставлять комментарии