Dela via


Enhets- och integreringstester i minimala API-appar

Anmärkning

Det här är inte den senaste versionen av den här artikeln. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i supportpolicyn för .NET och .NET Core. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Viktigt!

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Av Fiyaz Bin Hasan och Rick Anderson

Introduktion till integreringstester

Integreringstester utvärderar en apps komponenter på en bredare nivå än enhetstester. Enhetstester används för att testa isolerade programvarukomponenter, till exempel enskilda klassmetoder. Integreringstester bekräftar att två eller flera appkomponenter fungerar tillsammans för att skapa ett förväntat resultat, eventuellt inklusive varje komponent som krävs för att bearbeta en begäran fullt ut.

Dessa bredare tester används för att testa appens infrastruktur och hela ramverket, ofta med följande komponenter:

  • Databas
  • Filsystem
  • Nätverksinstallationer
  • Flöde för begäran-svar

Enhetstester använder fabricerade komponenter, så kallade falska eller falska objekt, i stället för infrastrukturkomponenter.

Till skillnad från enhetstester, integreringstester:

  • Använd de faktiska komponenter som appen använder i produktion.
  • Kräv mer kod och databearbetning.
  • Tar längre tid att genomföra.

Begränsa därför användningen av integreringstester till de viktigaste infrastrukturscenarierna. Om ett beteende kan testas med antingen ett enhetstest eller ett integreringstest väljer du enhetstestet.

I diskussioner om integreringstester kallas det testade projektet ofta System Under Test, eller "SUT" för kort. "SUT" används i hela den här artikeln för att referera till ASP.NET Core-appen som testas.

Skriv inte integreringstester för varje permutation av data och filåtkomst med databaser och filsystem. Oavsett hur många platser i en app som interagerar med databaser och filsystem kan en prioriterad uppsättning av integreringstester för läsning, skrivning, uppdatering och borttagning vanligtvis testa databas- och filsystemkomponenter på ett tillfredsställande sätt. Använd enhetstester för rutinmässiga tester av metodlogik som interagerar med dessa komponenter. I enhetstester resulterar användningen av förfalskningar eller hån i infrastrukturen i snabbare testkörning.

ASP.NET Core-integreringstester

Integreringstester i ASP.NET Core kräver följande:

  • Ett testprojekt används för att innehålla och köra testerna. Testprojektet har en referens till SUT.
  • Testprojektet skapar en testwebbvärd för SUT och använder en testserverklient för att hantera begäranden och svar med SUT.
  • En testkörare används för att köra testerna och rapportera testresultaten.

Integreringstester följer en sekvens av händelser som innehåller de vanliga teststegen Ordna, Agera, och Bekräfta:

  1. SUT:s webbhotell är konfigurerat.
  2. En testserverklient skapas för att skicka begäranden till appen.
  3. Teststeget Ordna körs: Testappen förbereder en begäran.
  4. Teststeget Act körs: Klienten skickar begäran och tar emot svaret.
  5. Teststeget Assert körs: Det faktiska svaret verifieras som antingen godkänd eller underkänd baserat på ett förväntat svar.
  6. Processen fortsätter tills alla tester körs.
  7. Testresultaten rapporteras.

Vanligtvis är testwebbvärden konfigurerad annorlunda än appens normala webbvärd för tester. Till exempel kan en annan databas eller olika appinställningar användas för testerna.

Infrastrukturkomponenter, till exempel testwebbvärden och minnesintern testserver (TestServer), tillhandahålls eller hanteras av Microsoft.AspNetCore.Mvc.Testing-paketet. Användning av det här paketet effektiviserar skapande och körning av test.

Microsoft.AspNetCore.Mvc.Testing-paketet hanterar följande uppgifter:

  • Kopierar beroendefilen (.deps) från SUT till testprojektets bin katalog.
  • Anger innehållsroten till SUT:s projektrot så att statiska filer och sidor/vyer hittas när testerna körs.
  • Tillhandahåller klassen WebApplicationFactory för att effektivisera initieringen av SUT tillsammans med TestServer.

I enhetstester dokumentationen beskrivs hur du konfigurerar ett testprojekt och en testlöpare, tillsammans med detaljerade instruktioner om hur du kör tester och rekommendationer för hur du namnger tester och testklasser.

Separera enhetstester från integreringstester i olika projekt. Separera testerna:

  • Hjälper till att säkerställa att komponenter för infrastrukturtestning inte oavsiktligt ingår i enhetstesterna.
  • Tillåter kontroll över vilken uppsättning tester som körs.

Exempelkoden på GitHub innehåller ett exempel på enhets- och integreringstester i en minimal API-app.

IResult-implementeringstyper för enhetstest

I följande exempel visas hur du enhetstestar minimala routningshanterare som returnerar IResult med hjälp av xUnit-testramverket . Den externa databasen ersätts med en minnesintern databas under testningen. Implementeringen av MockDb den finns i exempelkoden.

Offentliga IResult implementeringstyper i Microsoft.AspNetCore.Http.HttpResults namnområdet kan användas för att enhetstesta minimala routningshanterare när du använder namngivna metoder i stället för lambdas.

Följande kod använder klassen NotFound<TValue>:

[Fact]
public async Task GetTodoReturnsNotFoundIfNotExists()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var notFoundResult = (NotFound) result.Result;

    Assert.NotNull(notFoundResult);
}

Följande kod använder klassen Ok<TValue>:

[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    context.Todos.Add(new Todo
    {
        Id = 1,
        Title = "Test title",
        Description = "Test description",
        IsDone = false
    });

    await context.SaveChangesAsync();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var okResult = (Ok<Todo>)result.Result;

    Assert.NotNull(okResult.Value);
    Assert.Equal(1, okResult.Value.Id);
}

I föregående exempel omvandlas resultatet till en konkret typ eftersom slutpunkten under test kan returnera flera typer (a NotFound<TValue> eller Ok<TValue>) resultat. Men om slutpunkten returnerar en enda TypedResults typ härleds resultatet automatiskt till den typen och ingen gjutning krävs.

Följande kod använder Ok klassen och värdets typ är en samling med Todo:

[Fact]
public async Task GetAllReturnsTodosFromDatabase()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    context.Todos.Add(new Todo
    {
        Id = 1,
        Title = "Test title 1",
        Description = "Test description 1",
        IsDone = false
    });

    context.Todos.Add(new Todo
    {
        Id = 2,
        Title = "Test title 2",
        Description = "Test description 2",
        IsDone = true
    });

    await context.SaveChangesAsync();

    // Act
    var result = await TodoEndpointsV1.GetAllTodos(context);

    //Assert
    Assert.IsType<Ok<Todo[]>>(result);
    
    Assert.NotNull(result.Value);
    Assert.NotEmpty(result.Value);
    Assert.Collection(result.Value, todo1 =>
    {
        Assert.Equal(1, todo1.Id);
        Assert.Equal("Test title 1", todo1.Title);
        Assert.False(todo1.IsDone);
    }, todo2 =>
    {
        Assert.Equal(2, todo2.Id);
        Assert.Equal("Test title 2", todo2.Title);
        Assert.True(todo2.IsDone);
    });
}

Ytterligare resurser