Setup e infraestrutura de testes — SGApi
Guia para criar e configurar os projetos de teste, CI e fixtures compartilhadas.
1. Projetos NuGet
SGApi.UnitTests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.*" />
<PackageReference Include="NSubstitute" Version="5.*" />
<PackageReference Include="FluentAssertions" Version="7.*" />
<PackageReference Include="coverlet.collector" Version="6.*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SGApi Solution\SGApi.csproj" />
</ItemGroup>
</Project>
SGApi.IntegrationTests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.*" />
<PackageReference Include="FluentAssertions" Version="7.*" />
<PackageReference Include="coverlet.collector" Version="6.*" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.*" />
<PackageReference Include="Testcontainers.MySql" Version="4.*" />
<PackageReference Include="Respawn" Version="6.*" />
<PackageReference Include="Bogus" Version="35.*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SGApi Solution\SGApi.csproj" />
</ItemGroup>
</Project>
Ajuste versões conforme central package management do repositório, se existir.
2. Expor Program para integração
O Program.cs da SGApi usa top-level statements. Adicione no final do Program.cs ou em arquivo Program.IntegrationTests.cs:
public partial class Program { }
Sem isso, WebApplicationFactory<Program> não compila.
3. Ambiente Testing
Crie perfil lógico para desabilitar side effects:
| Componente | Em testes |
|---|---|
| Serilog → Seq | Desabilitar ou sink console only |
InitializeCloudCacheAsync | Skip ou mock |
| Migrations em background | Controlar manualmente no fixture |
Args --cloud | Forçar modo on-prem via factory |
Na factory:
builder.UseEnvironment("Testing");
No código de produção, onde necessário:
if (app.Environment.IsEnvironment("Testing"))
{
// skip Seq, skip cloud init, etc.
}
Preferir substituir serviços via ConfigureTestServices em vez de espalhar if (Testing) — use if só quando inevitável.
4. WebApplicationFactory base
public class SgApiWebApplicationFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly MySqlContainer _mysql = new MySqlBuilder()
.WithImage("mysql:8.0")
.Build();
public string ConnectionString => _mysql.GetConnectionString();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");
// Args equivalentes ao modo local on-prem
builder.UseSetting("Database:Server", _mysql.Hostname);
builder.UseSetting("Database:Port", _mysql.GetMappedPublicPort(3306).ToString());
// ... conforme DadosInicializacaoModel / ConnectionStringProvider
builder.ConfigureTestServices(services =>
{
// Substituir ICloudTenantCacheService se necessário
// JWT secret fixo para testes
});
}
public async Task InitializeAsync() => await _mysql.StartAsync();
public new async Task DisposeAsync()
{
await _mysql.DisposeAsync();
await base.DisposeAsync();
}
}
Pontos críticos SGApi
ConfigureTestServices, nãoConfigureServices— senão o DI de produção sobrescreve os mocks.- Connection string dinâmica do Testcontainers — nunca apontar para BD de dev/prod.
AddDatabaseContexts(dadosArgs)— passar args de teste compatíveis comConnectionStringProvider.- Schema legado — aplicar scripts de
MigrationScriptsou dump SQL mínimo noInitializeAsync.
5. Collection Fixture (performance)
Um container MySQL por suite, não por teste:
[CollectionDefinition("Integration")]
public class IntegrationCollection : ICollectionFixture<SgApiWebApplicationFactory> { }
[Collection("Integration")]
public class AjustesEstoqueIntegrationTests
{
private readonly SgApiWebApplicationFactory _factory;
public AjustesEstoqueIntegrationTests(SgApiWebApplicationFactory factory) => _factory = factory;
}
Desabilitar paralelismo na collection:
[assembly: CollectionBehavior(DisableTestParallelization = true)]
Ou marcar apenas a collection de integração com fixture compartilhada e rodar integração em job CI separado.
6. Reset de banco (Respawn)
public class DatabaseResetHelper
{
private Respawner _respawner = null!;
public async Task InitializeAsync(string connectionString)
{
await using var conn = new MySqlConnection(connectionString);
await conn.OpenAsync();
_respawner = await Respawner.CreateAsync(conn, new RespawnerOptions
{
DbAdapter = DbAdapter.MySql,
TablesToIgnore = new[] { "__EFMigrationsHistory" }
});
}
public async Task ResetAsync(string connectionString)
{
await using var conn = new MySqlConnection(connectionString);
await conn.OpenAsync();
await _respawner.ResetAsync(conn);
}
}
Chamar ResetAsync no início de cada teste (ou [ IAsyncLifetime ] na classe base).
7. JWT e permissões de teste
Helper para client autenticado:
public static class HttpClientExtensions
{
public static HttpClient CreateAuthenticatedClient(
this WebApplicationFactory<Program> factory,
int usuarioId = 1,
string[]? permissoes = null)
{
var client = factory.CreateClient();
var token = JwtTestTokenGenerator.Gerar(usuarioId, permissoes ?? []);
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
return client;
}
}
Claims devem refletir o que AuthorizePermission e SessaoTokenValidator exigem (incluir sessao_id se validação de sessão estiver ativa — mockar validador ou seed em superapp_usuarios).
8. Seed de dados
Centralize em DatabaseSeeder:
public static class DatabaseSeeder
{
public static async Task SeedAjusteEstoqueAsync(SglinxDbContext db)
{
// empresa, produto es1, estoques, permissões, mov table do mês, etc.
}
}
Regras:
- IDs fixos nos testes (
produtoId = 33610,lojaId = 2) para asserts previsíveis. - Seed mínimo por teste ou por classe — evitar dump completo do cliente.
- Tabelas dinâmicas
mov{empresa}estoque{MMYY}: criar no seed ou viaEstruturaMovEstoqueequivalente.
9. Executar localmente
# Unitários (rápido)
dotnet test SGApi.UnitTests
# Integração (requer Docker)
dotnet test SGApi.IntegrationTests
# Com cobertura
dotnet test SGApi.UnitTests --collect:"XPlat Code Coverage"
10. CI/CD
Implementado em .github/workflows/ via reusable-test.yml:
dotnet build SGApi.sln -c Releasedotnet test tests/SGApi.ArchitectureTests -c Debugdotnet test tests/SGApi.UnitTests -c DebugSGAPI_MYSQL_IMAGE=mysql:8.0emysql:5.5→tests/SGApi.IntegrationTests
E2E bloqueante (PR staging → main): tests/SGApi.E2ETests com secrets STAGING_*.
Configuração de secrets, branch protection e GHCR: .github/README.md.
Local: scripts/test.ps1.
11. Próximos passos
- Criar os dois projetos de teste na solution.
- Adicionar
public partial class Program. - Implementar
SgApiWebApplicationFactory+ seed mínimo. - Primeiro teste: testes de integração — ajuste de estoque.