Интеграционные тесты в 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);
}
}
Отмечу, что если вы не можете правильно выбрать именование для тестов, у нас была статья на эту тему.
Исходный код проекта можно найти тут.