Расширение фреймворка xUnit для выполнения интеграционного тестирования
$ dotnet add package Reo.Core.Xunit.IntegrationTestingРасширение фреймворка xUnit для выполнения интеграционного тестирования
В проекте с тестами необходимо определить файл со следующим содержимым:
using Reo.Core.IntegrationTesting.TestFramework.Mongo;
using Reo.Core.IntegrationTesting.TestFramework.Postgres;
using Reo.Core.Xunit.IntegrationTesting.Attributes;
[assembly:EnableIntegrationTestingFramework]
[assembly:RaiseContainer<PostgresTestContainer<TestingContext>>]
[assembly:RaiseContainer<MongoTestContainer>]
Атрибут EnableIntegrationTestingFramework должен быть указан в обязательном порядке. Он указывает xUnit, что необходимо использовать расширенный тестовый фреймворк вместо обычного.
Атрибут RaiseContainer нужен для того, чтобы при запуске тестов запустился контейнер указанного типа. В прошлом контейнеры запускались при старте каждого тестового класса, теперь запускается единственный контейнер для всех тестов примерно сразу после загрузки сборки.
На данный момент реализованы четыре контейнера (их можно найти в пакете Reo.Core.IntegrationTesting):
В тестовом классе необходимо указать какую фикстуру вы хотите использовать.
CollectionFixture
Фикстура создается один раз на запускаемую пачку тестов
// CollectionDefinition.cs
[CollectionDefinition(nameof(PostgresDefinition))]
public sealed class PostgresDefinition : ICollectionFixture<PostgresFixture<TestingDbContext>>
{ }
// TestClass.cs
[Collection(nameof(PostgresDefinition))]
public sealed class TestClass
{
private readonly PostgresFixture<TestingDbContext> _fixture;
public TestClass(PostgresFixture<TestingDbContext> fixture)
{
_fixture = fixture;
}
[Fact]
public void Verify()
{
// ...
}
}
К сожалению, CollectionDefinition необходимо описывать в каждой сборке, иначе xUnit их не увидит (см. документацию xUnit)
ClassFixture
Фикстура создается один раз на запускаемый тестовый класс
public sealed class TestClass : IClassFixture<MongoFixture>
{
private readonly MongoFixture _fixture;
public TestClass(MongoFixture fixture)
{
_fixture = fixture;
}
[Fact]
public void Verify()
{
// ...
}
}И то, и другое
xUnit не запрещает внедрять IClassFixture и ICollectionFixture одновременно:
[Collection(nameof(PostgresDefinition))]
public sealed class TestClass : IClassFixture<MongoFixture>
{
// ...
public TestClass(PostgresFixture<TestingDbContext> postgresFixture, MongoFixture mongoFixture)
{
// ...
}
// ...
}Чтобы проинициализировать справочники, вы должны реализовать абстрактный класс ContainerSeeder
public sealed class PostgresSeeder : ContainerSeeder<PostgresFixture<TestingContext>>
{
/// <inheritdoc />
public override async Task SeedAsync(PostgresFixture<TestingContext> fixture)
{
await using var databaseContext =
await fixture.DatabaseContextFactory.CreateDbContextAsync();
databaseContext.References.Add(new()
{
Id = Guid.NewGuid(),
Name = "Profile test"
});
await databaseContext.SaveChangesAsync();
}
}Сид не должен содержать конструкторов, кроме стандартного. Количество сидов для одной фикстуры не ограничено.
Немного про очистку базы данных после исполнения конкретного теста
Если после каждого теста вы хотите откатывать ее в первоначальное состояние - используйте метод CleanupAsync, определенной у каждой фикстуры:
public sealed class Tests : IClassFixture<PostgresFixture<TestingContext>>, IAsyncLifetime
{
private readonly PostgresFixture<TestingContext> _fixture;
public ContainerSeederTests(PostgresFixture<TestingContext> fixture)
=> _fixture = fixture;
public async Task InitializeAsync()
{
await using var databaseContext =
await _fixture.DatabaseContextFactory.CreateDbContextAsync();
databaseContext.Entities.Add(new()
{
Id = Guid.NewGuid()
});
await databaseContext.SaveChangesAsync();
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Verify(int _)
{
// Благодаря _fixture.CleanupAsync() в базе всегда будет 1 запись, добавленная в InitializeAsync()
}
public Task DisposeAsync()
=> _fixture.CleanupAsync();
}Метод CleanupAsync очищает базу данных и повторно выполняет сидирование справочников
При внедрении фикстуры используйте готовые методы расширения:
public sealed class TestClass :
IClassFixture<PostgresFixture<TestingDbContext>>,
IClassFixture<MongoFixture>,
IClassFixture<ElasticFixture>,
IClassFixture<RedisFixture>
{
private readonly AutoMocker _mocker = new();
// ...
public TestClass(
PostgresFixture<TestingDbContext> postgresFixture,
MongoFixture mongoFixture,
ElasticFixture elasticFixture,
RedisFixture redisFixture)
{
// ...
_mocker
.SetupPostgres(postgresFixture)
.SetupMongo(mongoFixture)
.SetupElastic(elasticFixture)
.SetupRedis(redisFixture);
}
// ...
}