Parte 4 — Cheatsheet
Pagină de referință rapidă pentru tot ce ați văzut în lab. Folosiți-o ca aide-mémoire la următorul dotnet test.
xUnit — atribute esențiale
| Atribut / interfață | Când | Exemplu |
|---|---|---|
[Fact] |
Test fără parametri | [Fact] public void X_DoesY() |
[Theory] + [InlineData(...)] |
Același test, multiple input-uri | [Theory] [InlineData(1,2,3)] |
| Constructor | Setup per test (instanță nouă) | public MyTests() { ... } |
IDisposable.Dispose |
Teardown sincron per test | public void Dispose() { ... } |
IAsyncLifetime |
Setup/teardown async per test | InitializeAsync / DisposeAsync |
IClassFixture<T> |
O instanță T partajată în clasă |
class Tests : IClassFixture<Factory> |
[Trait("Category","Slow")] |
Categorie pentru filtrare | dotnet test --filter Category=Slow |
Convenția de naming: Metodă_Scenariu_RezultatAșteptat — Update_AsNonOwner_Returns403.
Assert.* — tabelul de buzunar
| Assert | Verifică |
|---|---|
Assert.Equal(expected, actual) |
Valori egale |
Assert.NotEqual(expected, actual) |
Valori diferite |
Assert.Null(obj) / Assert.NotNull(obj) |
Null check |
Assert.Empty(collection) / Assert.Single(collection) |
0 / 1 element |
Assert.Equal(n, collection.Count) |
Count exact |
Assert.Contains(item, collection) |
Conține elementul |
Assert.True(condition) / Assert.False(condition) |
Boolean |
Assert.IsType<T>(obj) |
Tip exact |
Assert.IsAssignableFrom<T>(obj) |
Tip sau derivat |
Assert.Throws<T>(() => ...) |
Aruncă T (sync) |
await Assert.ThrowsAsync<T>(async () => ...) |
Aruncă T (async) |
Dacă lucrați pe un repo care folosește FluentAssertions, echivalentele sunt
actual.Should().Be(expected),result.Should().NotBeNull(),items.Should().HaveCount(3),act.Should().Throw<T>(). Conceptual același lucru, sintaxă diferită.
EF Core InMemory — pattern pentru service tests
private static (ArticleService service, AppDbContext context) CreateService(string dbName)
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: dbName)
.Options;
var context = new AppDbContext(options);
var unitOfWork = new UnitOfWork(context);
var service = new ArticleService(unitOfWork);
return (service, context);
}
Reguli:
dbNameunic per test — folosiținameof(metoda_test)sau interpolat cu parametrii din[Theory]. Două teste cu acelașidbNameîmpart datele.SaveChanges()după seed — change tracker-ul nu e BD; fărăSaveChanges, query-urile nu văd nimic.- InMemory ≠ SQL Server. Tranzacțiile sunt no-op, FK/UNIQUE nu sunt validate, funcțiile SQL specifice (
STRING_AGG,JSON_VALUE) pică la runtime. Pentru realism 100%: Testcontainers.
Pattern AAA — mereu
[Fact]
public async Task Name()
{
// Arrange — pregătește input + stack-ul
var (service, context) = CreateService(nameof(Name));
SeedTwoArticles(context);
// Act — o singură linie de obicei
var result = await service.GetByIdAsync(1);
// Assert — verifică
Assert.NotNull(result);
Assert.Equal("Articol test 1", result!.Title);
}
WebApplicationFactory<T> — boilerplate
// Program.cs — ULTIMA LINIE, esențială
public partial class Program { }
// CustomWebApplicationFactory.cs
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
private readonly string _dbName = $"TestDb_{Guid.NewGuid()}";
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");
builder.ConfigureServices(services =>
{
var d = services.SingleOrDefault(s =>
s.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (d != null) services.Remove(d);
services.AddDbContext<AppDbContext>(o => o.UseInMemoryDatabase(_dbName));
});
}
}
// În test class
public class MyTests : IClassFixture<CustomWebApplicationFactory>, IAsyncLifetime
{
private readonly HttpClient _client;
public MyTests(CustomWebApplicationFactory f) => _client = f.CreateClient();
public Task InitializeAsync() => Task.CompletedTask;
public Task DisposeAsync() => Task.CompletedTask;
}
HttpClient — request-uri pentru integration tests
// GET + JSON
var r = await _client.GetAsync("/api/articlesapi");
var dtos = await r.Content.ReadFromJsonAsync<List<ArticleDto>>();
// POST cu body JSON
var r = await _client.PostAsJsonAsync("/api/articlesapi", dto);
// POST/PUT cu Authorization (pe request individual)
var req = new HttpRequestMessage(HttpMethod.Post, "/api/articlesapi")
{
Content = JsonContent.Create(dto)
};
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var r = await _client.SendAsync(req);
// status code assertions
Assert.Equal(HttpStatusCode.OK, r.StatusCode); // 200
Assert.Equal(HttpStatusCode.Created, r.StatusCode); // 201
Assert.Equal(HttpStatusCode.NoContent, r.StatusCode); // 204
Assert.Equal(HttpStatusCode.Unauthorized, r.StatusCode); // 401
Assert.Equal(HttpStatusCode.Forbidden, r.StatusCode); // 403
Assert.Equal(HttpStatusCode.NotFound, r.StatusCode); // 404
CLI — comenzi utile
# rulează toate testele
dotnet test
# rulează doar o clasă / un test
dotnet test --filter "FullyQualifiedName~ArticleServiceTests"
dotnet test --filter "Name=GetByIdAsync_ExistingId_ReturnsArticle"
# coverage (necesită coverlet.collector în csproj)
dotnet test --collect:"XPlat Code Coverage"
# verbose (vezi ce teste rulează)
dotnet test --logger "console;verbosity=detailed"
# rulare paralelă oprită (util când două clase folosesc aceeași BD InMemory)
dotnet test -- xUnit.ParallelizeAssembly=false xUnit.ParallelizeTestCollections=false
Decision tree: ce fel de test scriu?
- Logica de business (service + repo + DB) → unit test cu InMemory, stack real (Partea 2).
- Routing, model binding,
[ApiController], JSON, validare → integration test cuWebApplicationFactory(Partea 3). - Auth real (cookie/JWT),
[Authorize]→ integration test cu login real prin endpoint. - Comportament SQL specific (concurrency, FK, funcții T-SQL) → Testcontainers, nu InMemory.
- Funcție pură (parser, calcul) → unit test cu
[Fact]/[Theory], fără mock-uri și fără DB.
Linkuri rapide
- xUnit docs · Shared context (
IClassFixture,ICollectionFixture) - EF Core — testing without the database
- Integration tests in ASP.NET Core
- JWT bearer auth în ASP.NET Core · jwt.io
- FluentAssertions (alternativă la
Assert.*) · Moq (mocking când chiar trebuie) · Testcontainers for .NET