Test di integrazione in ASP.NET CoreIntegration tests in ASP.NET Core

Di Luke Latham, Javier Calvarro Nelson, Steve Smithe Jos van der tilBy Luke Latham, Javier Calvarro Nelson, Steve Smith, and Jos van der Til

I test di integrazione assicurano che i componenti di un'app funzionino correttamente a un livello che include l'infrastruttura di supporto dell'app, ad esempio il database, la file system e la rete.Integration tests ensure that an app's components function correctly at a level that includes the app's supporting infrastructure, such as the database, file system, and network. ASP.NET Core supporta i test di integrazione utilizzando un framework unit test con un host Web di prova e un server di prova in memoria.ASP.NET Core supports integration tests using a unit test framework with a test web host and an in-memory test server.

In questo argomento si presuppone una conoscenza di base degli unit test.This topic assumes a basic understanding of unit tests. Se non si ha familiarità con i concetti di test, vedere gli unit test in .NET Core e .NET standard argomento e il contenuto collegato.If unfamiliar with test concepts, see the Unit Testing in .NET Core and .NET Standard topic and its linked content.

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

L'app di esempio è un'app Razor Pages e presuppone una conoscenza di base delle Razor Pages.The sample app is a Razor Pages app and assumes a basic understanding of Razor Pages. Se non si ha familiarità con Razor Pages, vedere gli argomenti seguenti:If unfamiliar with Razor Pages, see the following topics:

Nota

Per il test di Spa è stato consigliato uno strumento come Selenium, che consente di automatizzare un browser.For testing SPAs, we recommended a tool such as Selenium, which can automate a browser.

Introduzione ai test di integrazioneIntroduction to integration tests

I test di integrazione valutano i componenti di un'app a un livello più ampio rispetto agli unit test.Integration tests evaluate an app's components on a broader level than unit tests. Gli unit test vengono utilizzati per testare i componenti software isolati, ad esempio i singoli metodi di classe.Unit tests are used to test isolated software components, such as individual class methods. I test di integrazione confermano che due o più componenti dell'app interagiscono per produrre un risultato previsto, eventualmente includendo tutti i componenti necessari per elaborare completamente una richiesta.Integration tests confirm that two or more app components work together to produce an expected result, possibly including every component required to fully process a request.

Questi test più ampi vengono usati per testare l'infrastruttura dell'app e l'intero Framework, spesso inclusi i componenti seguenti:These broader tests are used to test the app's infrastructure and whole framework, often including the following components:

  • Database diDatabase
  • File systemFile system
  • Appliance di reteNetwork appliances
  • Pipeline richiesta-rispostaRequest-response pipeline

Gli unit test utilizzano componenti fabbricati, noti come Fakes o oggetti fittizi, al posto dei componenti dell'infrastruttura.Unit tests use fabricated components, known as fakes or mock objects, in place of infrastructure components.

Diversamente dagli unit test, i test di integrazione:In contrast to unit tests, integration tests:

  • Usare i componenti effettivi usati dall'app nell'ambiente di produzione.Use the actual components that the app uses in production.
  • Richiedere più codice e l'elaborazione dei dati.Require more code and data processing.
  • Richiedere più tempo per l'esecuzione.Take longer to run.

Pertanto, limitare l'utilizzo dei test di integrazione ai più importanti scenari di infrastruttura.Therefore, limit the use of integration tests to the most important infrastructure scenarios. Se è possibile testare un comportamento utilizzando un unit test o un test di integrazione, scegliere il unit test.If a behavior can be tested using either a unit test or an integration test, choose the unit test.

Suggerimento

Non scrivere test di integrazione per ogni possibile permutazione di dati e accesso ai file con database e file System.Don't write integration tests for every possible permutation of data and file access with databases and file systems. Indipendentemente dal numero di posizioni in un'app che interagiscono con i database e i file System, un set mirato di test di integrazione di lettura, scrittura, aggiornamento ed eliminazione è in genere in grado di testare adeguatamente i componenti di database e file system.Regardless of how many places across an app interact with databases and file systems, a focused set of read, write, update, and delete integration tests are usually capable of adequately testing database and file system components. Usare gli unit test per i test di routine della logica del metodo che interagiscono con questi componenti.Use unit tests for routine tests of method logic that interact with these components. Negli unit test, l'uso di Fakes/Mocks dell'infrastruttura comporta una maggiore esecuzione dei test.In unit tests, the use of infrastructure fakes/mocks result in faster test execution.

Nota

Nelle discussioni sui test di integrazione, il progetto testato viene spesso definito sistemasottoposto a test oppure "Sut" per brevità.In discussions of integration tests, the tested project is frequently called the System Under Test, or "SUT" for short.

"SUT" viene usato in questo argomento per fare riferimento all'app ASP.NET Core testata."SUT" is used throughout this topic to refer to the tested ASP.NET Core app.

Test di integrazione di ASP.NET CoreASP.NET Core integration tests

I test di integrazione in ASP.NET Core richiedono gli elementi seguenti:Integration tests in ASP.NET Core require the following:

  • Un progetto di test viene utilizzato per contenere ed eseguire i test.A test project is used to contain and execute the tests. Il progetto di test contiene un riferimento a SUT.The test project has a reference to the SUT.
  • Il progetto di test crea un host Web di test per SUT e usa un client del server di prova per gestire le richieste e le risposte con SUT.The test project creates a test web host for the SUT and uses a test server client to handle requests and responses with the SUT.
  • Viene usato un test runner per eseguire i test e segnalare i risultati del test.A test runner is used to execute the tests and report the test results.

I test di integrazione seguono una sequenza di eventi che includono i normali passi del test Arrange, Acte Assert :Integration tests follow a sequence of events that include the usual Arrange, Act, and Assert test steps:

  1. L'host Web di SUT è configurato.The SUT's web host is configured.
  2. Viene creato un client del server di prova per inviare richieste all'app.A test server client is created to submit requests to the app.
  3. Il passaggio di disposizione del test viene eseguito: l'app di test prepara una richiesta.The Arrange test step is executed: The test app prepares a request.
  4. Viene eseguito il passaggio del test Act : il client invia la richiesta e riceve la risposta.The Act test step is executed: The client submits the request and receives the response.
  5. Viene eseguito il passaggio del test Assert : la risposta effettiva viene convalidata come superato o non superato in base a una risposta prevista .The Assert test step is executed: The actual response is validated as a pass or fail based on an expected response.
  6. Il processo continua fino a quando non vengono eseguiti tutti i test.The process continues until all of the tests are executed.
  7. Vengono restituiti i risultati del test.The test results are reported.

In genere, l'host Web di test viene configurato in modo diverso rispetto al normale host web dell'app per le esecuzioni dei test.Usually, the test web host is configured differently than the app's normal web host for the test runs. Ad esempio, è possibile usare un database diverso o impostazioni di app diverse per i test.For example, a different database or different app settings might be used for the tests.

I componenti dell'infrastruttura, ad esempio l'host Web di test e il server di prova in memoria (TestServer), vengono forniti o gestiti dal pacchetto Microsoft. AspNetCore. Mvc. testing .Infrastructure components, such as the test web host and in-memory test server (TestServer), are provided or managed by the Microsoft.AspNetCore.Mvc.Testing package. L'utilizzo di questo pacchetto semplifica la creazione e l'esecuzione di test.Use of this package streamlines test creation and execution.

Il pacchetto di Microsoft.AspNetCore.Mvc.Testing gestisce le attività seguenti:The Microsoft.AspNetCore.Mvc.Testing package handles the following tasks:

  • Copia il file delle dipendenze ( . Deps) da SUT nella directory bin del progetto di test.Copies the dependencies file (.deps) from the SUT into the test project's bin directory.
  • Imposta la radice del contenuto sulla radice del progetto di Sut in modo che i file e le pagine e le visualizzazioni statiche vengano rilevati durante l'esecuzione dei test.Sets the content root to the SUT's project root so that static files and pages/views are found when the tests are executed.
  • Fornisce la classe WebApplicationFactory per semplificare il bootstrap del SUT con TestServer.Provides the WebApplicationFactory class to streamline bootstrapping the SUT with TestServer.

Nella documentazione relativa agli unit test viene descritto come configurare un progetto di test e un test runner, oltre a istruzioni dettagliate su come eseguire i test e le indicazioni su come denominare i test e le classi di test.The unit tests documentation describes how to set up a test project and test runner, along with detailed instructions on how to run tests and recommendations for how to name tests and test classes.

Nota

Quando si crea un progetto di test per un'app, separare gli unit test dai test di integrazione in progetti diversi.When creating a test project for an app, separate the unit tests from the integration tests into different projects. Ciò consente di garantire che i componenti di test dell'infrastruttura non vengano accidentalmente inclusi negli unit test.This helps ensure that infrastructure testing components aren't accidentally included in the unit tests. La separazione dei test di unità e integrazione consente inoltre di controllare il set di test da eseguire.Separation of unit and integration tests also allows control over which set of tests are run.

Non esiste praticamente alcuna differenza tra la configurazione per i test delle app Razor Pages e delle app MVC.There's virtually no difference between the configuration for tests of Razor Pages apps and MVC apps. L'unica differenza consiste nel modo in cui i test vengono denominati.The only difference is in how the tests are named. In un'app Razor Pages, i test degli endpoint della pagina vengono in genere denominati dopo la classe del modello di pagina, ad esempio IndexPageTests per testare l'integrazione dei componenti per la pagina di indice.In a Razor Pages app, tests of page endpoints are usually named after the page model class (for example, IndexPageTests to test component integration for the Index page). In un'app MVC i test sono in genere organizzati per classi controller e denominati dopo i controller testati, ad esempio HomeControllerTests per testare l'integrazione dei componenti per il controller Home.In an MVC app, tests are usually organized by controller classes and named after the controllers they test (for example, HomeControllerTests to test component integration for the Home controller).

Test prerequisiti appTest app prerequisites

Il progetto di test deve:The test project must:

Questi prerequisiti possono essere visualizzati nell' app di esempio.These prerequisites can be seen in the sample app. Esaminare il file tests/RazorPagesProject. tests/RazorPagesProject. tests. csproj .Inspect the tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj file. L'app di esempio usa il Framework di test xUnit e la libreria del parser AngleSharp , quindi l'app di esempio fa riferimento anche a:The sample app uses the xUnit test framework and the AngleSharp parser library, so the sample app also references:

Entity Framework Core viene inoltre usato nei test.Entity Framework Core is also used in the tests. L'app fa riferimento a:The app references:

Ambiente SUTSUT environment

Se l' ambiente di Sut non è impostato, il valore predefinito dell'ambiente è Development.If the SUT's environment isn't set, the environment defaults to Development.

Test di base con WebApplicationFactory predefinitoBasic tests with the default WebApplicationFactory

WebApplicationFactory<TEntryPoint > viene usato per creare un TestServer per i test di integrazione.WebApplicationFactory<TEntryPoint> is used to create a TestServer for the integration tests. TEntryPoint è la classe del punto di ingresso di SUT, in genere la classe Startup.TEntryPoint is the entry point class of the SUT, usually the Startup class.

Le classi di test implementano una classe fixture Interface (IClassFixture) per indicare che la classe contiene test e fornisce istanze di oggetti condivisi tra i test nella classe.Test classes implement a class fixture interface (IClassFixture) to indicate the class contains tests and provide shared object instances across the tests in the class.

La classe di test seguente, BasicTests, usa il WebApplicationFactory per avviare il SUT e fornire un HttpClient a un metodo di test, Get_EndpointsReturnSuccessAndCorrectContentType.The following test class, BasicTests, uses the WebApplicationFactory to bootstrap the SUT and provide an HttpClient to a test method, Get_EndpointsReturnSuccessAndCorrectContentType. Il metodo controlla se il codice di stato della risposta ha esito positivo (codici di stato nell'intervallo 200-299) e l'intestazione del Content-Type è text/html; charset=utf-8 per diverse pagine dell'app.The method checks if the response status code is successful (status codes in the range 200-299) and the Content-Type header is text/html; charset=utf-8 for several app pages.

CreateClient crea un'istanza di HttpClient che segue automaticamente i reindirizzamenti e gestisce i cookie.CreateClient creates an instance of HttpClient that automatically follows redirects and handles cookies.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
    private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;

    public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}

Per impostazione predefinita, i cookie non essenziali non vengono conservati nelle richieste quando i criteri di consenso GDPR sono abilitati.By default, non-essential cookies aren't preserved across requests when the GDPR consent policy is enabled. Per mantenere cookie non essenziali, ad esempio quelli usati dal provider TempData, contrassegnarli come essenziali nei test.To preserve non-essential cookies, such as those used by the TempData provider, mark them as essential in your tests. Per istruzioni su come contrassegnare un cookie come essenziale, vedere cookie essenziali.For instructions on marking a cookie as essential, see Essential cookies.

Personalizzare WebApplicationFactoryCustomize WebApplicationFactory

La configurazione dell'host Web può essere creata indipendentemente dalle classi di test ereditando da WebApplicationFactory per creare una o più factory personalizzate:Web host configuration can be created independently of the test classes by inheriting from WebApplicationFactory to create one or more custom factories:

  1. Ereditare da WebApplicationFactory ed eseguire l'override di ConfigureWebHost.Inherit from WebApplicationFactory and override ConfigureWebHost. IWebHostBuilder consente la configurazione della raccolta di servizi con ConfigureServices:The IWebHostBuilder allows the configuration of the service collection with ConfigureServices:

    public class CustomWebApplicationFactory<TStartup>
        : WebApplicationFactory<TStartup> where TStartup: class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                // Remove the app's ApplicationDbContext registration.
                var descriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbContextOptions<ApplicationDbContext>));
    
                if (descriptor != null)
                {
                    services.Remove(descriptor);
                }
    
                // Add ApplicationDbContext using an in-memory database for testing.
                services.AddDbContext<ApplicationDbContext>(options =>
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                });
    
                // Build the service provider.
                var sp = services.BuildServiceProvider();
    
                // Create a scope to obtain a reference to the database
                // context (ApplicationDbContext).
                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
    
                    // Ensure the database is created.
                    db.Database.EnsureCreated();
    
                    try
                    {
                        // Seed the database with test data.
                        Utilities.InitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding the " +
                            "database with test messages. Error: {Message}", ex.Message);
                    }
                }
            });
        }
    }
    

    Il seeding del database nell' app di esempio viene eseguito dal metodo InitializeDbForTests.Database seeding in the sample app is performed by the InitializeDbForTests method. Il metodo è descritto nella sezione esempio di test di integrazione: organizzazione di app di test .The method is described in the Integration tests sample: Test app organization section.

    Il contesto di database di SUT viene registrato nel metodo Startup.ConfigureServices.The SUT's database context is registered in its Startup.ConfigureServices method. Il callback builder.ConfigureServices dell'app di test viene eseguito dopo l'esecuzione del codice Startup.ConfigureServices dell'app.The test app's builder.ConfigureServices callback is executed after the app's Startup.ConfigureServices code is executed. L'ordine di esecuzione è una modifica di rilievo per l' host generico con la versione di ASP.NET Core 3,0.The execution order is a breaking change for the Generic Host with the release of ASP.NET Core 3.0. Per usare un database diverso per i test rispetto al database dell'app, è necessario sostituire il contesto di database dell'app in builder.ConfigureServices.To use a different database for the tests than the app's database, the app's database context must be replaced in builder.ConfigureServices.

    L'app di esempio trova il descrittore del servizio per il contesto del database e usa il descrittore per rimuovere la registrazione del servizio.The sample app finds the service descriptor for the database context and uses the descriptor to remove the service registration. Successivamente, la Factory aggiunge una nuova ApplicationDbContext che usa un database in memoria per i test.Next, the factory adds a new ApplicationDbContext that uses an in-memory database for the tests.

    Per connettersi a un database diverso dal database in memoria, modificare la chiamata UseInMemoryDatabase per connettere il contesto a un database diverso.To connect to a different database than the in-memory database, change the UseInMemoryDatabase call to connect the context to a different database. Per utilizzare un database di test SQL Server:To use a SQL Server test database:

    services.AddDbContext<ApplicationDbContext>((options, context) => 
    {
        context.UseSqlServer(
            Configuration.GetConnectionString("TestingDbConnectionString"));
    });
    
  2. Usare la CustomWebApplicationFactory personalizzata nelle classi di test.Use the custom CustomWebApplicationFactory in test classes. Nell'esempio seguente viene usata la Factory nella classe IndexPageTests:The following example uses the factory in the IndexPageTests class:

    public class IndexPageTests : 
        IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<RazorPagesProject.Startup> 
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
                {
                    AllowAutoRedirect = false
                });
        }
    

    Il client dell'app di esempio è configurato in modo da impedire il HttpClient dai reindirizzamenti seguenti.The sample app's client is configured to prevent the HttpClient from following redirects. Come illustrato più avanti nella sezione autenticazione fittizia , questo consente ai test di verificare il risultato della prima risposta dell'app.As explained later in the Mock authentication section, this permits tests to check the result of the app's first response. La prima risposta è un reindirizzamento in molti di questi test con un'intestazione Location.The first response is a redirect in many of these tests with a Location header.

  3. Un test tipico usa i metodi di HttpClient e helper per elaborare la richiesta e la risposta:A typical test uses the HttpClient and helper methods to process the request and the response:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    

Qualsiasi richiesta POST a SUT deve soddisfare il controllo antifalsificazione creato automaticamente dal sistema antifalsificazione di protezione dei datidell'app.Any POST request to the SUT must satisfy the antiforgery check that's automatically made by the app's data protection antiforgery system. Per disporre la richiesta POST di un test, l'app di test deve:In order to arrange for a test's POST request, the test app must:

  1. Effettuare una richiesta per la pagina.Make a request for the page.
  2. Analizzare il cookie antifalsificazione e richiedere il token di convalida dalla risposta.Parse the antiforgery cookie and request validation token from the response.
  3. Eseguire la richiesta POST con il cookie antifalsificazione e il token di convalida della richiesta sul posto.Make the POST request with the antiforgery cookie and request validation token in place.

I metodi di estensione SendAsync Helper (Helper/Metodo HttpClientExtensions. cs) e il metodo helper GetDocumentAsync (Helper/htmlhelpers. cs) nell' app di esempio usano il parser AngleSharp per gestire il controllo antifalsificazione con i metodi seguenti:The SendAsync helper extension methods (Helpers/HttpClientExtensions.cs) and the GetDocumentAsync helper method (Helpers/HtmlHelpers.cs) in the sample app use the AngleSharp parser to handle the antiforgery check with the following methods:

  • GetDocumentAsync – riceve HttpResponseMessage e restituisce un IHtmlDocument.GetDocumentAsync – Receives the HttpResponseMessage and returns an IHtmlDocument. GetDocumentAsync usa una factory che prepara una risposta virtuale basata sul HttpResponseMessageoriginale.GetDocumentAsync uses a factory that prepares a virtual response based on the original HttpResponseMessage. Per ulteriori informazioni, vedere la documentazione di AngleSharp.For more information, see the AngleSharp documentation.
  • SendAsync metodi di estensione per la HttpClient comporre un HttpRequestMessage e chiamare SendAsync (HttpRequestMessage) per inviare richieste a SUT.SendAsync extension methods for the HttpClient compose an HttpRequestMessage and call SendAsync(HttpRequestMessage) to submit requests to the SUT. Gli overload per SendAsync accettano il form HTML (IHtmlFormElement) e gli elementi seguenti:Overloads for SendAsync accept the HTML form (IHtmlFormElement) and the following:
    • Pulsante Invia del modulo (IHtmlElement)Submit button of the form (IHtmlElement)
    • Raccolta di valori di form (IEnumerable<KeyValuePair<string, string>>)Form values collection (IEnumerable<KeyValuePair<string, string>>)
    • Pulsante Invia (IHtmlElement) e valori form (IEnumerable<KeyValuePair<string, string>>)Submit button (IHtmlElement) and form values (IEnumerable<KeyValuePair<string, string>>)

Nota

AngleSharp è una libreria di analisi di terze parti usata a scopo dimostrativo in questo argomento e nell'app di esempio.AngleSharp is a third-party parsing library used for demonstration purposes in this topic and the sample app. AngleSharp non è supportato o richiesto per i test di integrazione delle app ASP.NET Core.AngleSharp isn't supported or required for integration testing of ASP.NET Core apps. È possibile usare altri parser, ad esempio HTML Agility Pack (HAP).Other parsers can be used, such as the Html Agility Pack (HAP). Un altro approccio consiste nel scrivere codice per gestire direttamente il token di verifica delle richieste del sistema antifalsificazione e il cookie antifalsificazione.Another approach is to write code to handle the antiforgery system's request verification token and antiforgery cookie directly.

Personalizzare il client con WithWebHostBuilderCustomize the client with WithWebHostBuilder

Quando è richiesta una configurazione aggiuntiva in un metodo di test, WithWebHostBuilder crea un nuovo oggetto WebApplicationFactory con un IWebHostBuilder che viene ulteriormente personalizzato in base alla configurazione.When additional configuration is required within a test method, WithWebHostBuilder creates a new WebApplicationFactory with an IWebHostBuilder that is further customized by configuration.

Il metodo di test Post_DeleteMessageHandler_ReturnsRedirectToRoot dell' app di esempio illustra l'uso di WithWebHostBuilder.The Post_DeleteMessageHandler_ReturnsRedirectToRoot test method of the sample app demonstrates the use of WithWebHostBuilder. Questo test esegue l'eliminazione di un record nel database attivando un invio di form in SUT.This test performs a record delete in the database by triggering a form submission in the SUT.

Poiché un altro test nella classe IndexPageTests esegue un'operazione che elimina tutti i record nel database e può essere eseguita prima del metodo Post_DeleteMessageHandler_ReturnsRedirectToRoot, il database viene sottoposta a reseeding in questo metodo di test per assicurarsi che sia presente un record per il SUT da eliminare.Because another test in the IndexPageTests class performs an operation that deletes all of the records in the database and may run before the Post_DeleteMessageHandler_ReturnsRedirectToRoot method, the database is reseeded in this test method to ensure that a record is present for the SUT to delete. La selezione del primo pulsante Elimina del modulo messages in SUT viene simulata nella richiesta al SUT:Selecting the first delete button of the messages form in the SUT is simulated in the request to the SUT:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                var serviceProvider = services.BuildServiceProvider();

                using (var scope = serviceProvider.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices
                        .GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<IndexPageTests>>();

                    try
                    {
                        Utilities.ReinitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding " +
                            "the database with test messages. Error: {Message}", 
                            ex.Message);
                    }
                }
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}

Opzioni clientClient options

Nella tabella seguente viene illustrato il valore predefinito di WebApplicationFactoryClientOptions disponibile durante la creazione di istanze HttpClient.The following table shows the default WebApplicationFactoryClientOptions available when creating HttpClient instances.

OpzioneOption DescrizioneDescription DefaultDefault
AllowAutoRedirectAllowAutoRedirect Ottiene o imposta un valore che indica se le istanze HttpClient devono seguire automaticamente le risposte di reindirizzamento.Gets or sets whether or not HttpClient instances should automatically follow redirect responses. true
BaseAddressBaseAddress Ottiene o imposta l'indirizzo di base delle istanze di HttpClient.Gets or sets the base address of HttpClient instances. http://localhost
HandleCookiesHandleCookies Ottiene o imposta un valore che indica se le istanze di HttpClient devono gestire i cookie.Gets or sets whether HttpClient instances should handle cookies. true
MaxAutomaticRedirectionsMaxAutomaticRedirections Ottiene o imposta il numero massimo di risposte di reindirizzamento che HttpClient istanze devono seguire.Gets or sets the maximum number of redirect responses that HttpClient instances should follow. 77

Creare la classe WebApplicationFactoryClientOptions e passarla al metodo CreateClient (i valori predefiniti sono mostrati nell'esempio di codice):Create the WebApplicationFactoryClientOptions class and pass it to the CreateClient method (default values are shown in the code example):

// Default client option values are shown
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.AllowAutoRedirect = true;
clientOptions.BaseAddress = new Uri("http://localhost");
clientOptions.HandleCookies = true;
clientOptions.MaxAutomaticRedirections = 7;

_client = _factory.CreateClient(clientOptions);

Inserire i servizi fittiziInject mock services

È possibile eseguire l'override dei servizi in un test con una chiamata a ConfigureTestServices nel generatore host.Services can be overridden in a test with a call to ConfigureTestServices on the host builder. Per inserire i servizi fittizi, SUT deve avere una classe Startup con un metodo di Startup.ConfigureServices.To inject mock services, the SUT must have a Startup class with a Startup.ConfigureServices method.

L'esempio SUT include un servizio con ambito che restituisce un'offerta.The sample SUT includes a scoped service that returns a quote. La virgoletta è incorporata in un campo nascosto nella pagina di indice quando viene richiesta la pagina di indice.The quote is embedded in a hidden field on the Index page when the Index page is requested.

Services/IQuoteService.cs:Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Startup.cs:Startup.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

Quando viene eseguita l'app SUT, viene generato il markup seguente:The following markup is generated when the SUT app is run:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

Per testare il servizio e l'inserimento di virgolette in un test di integrazione, un servizio fittizio viene inserito in SUT dal test.To test the service and quote injection in an integration test, a mock service is injected into the SUT by the test. Il servizio fittizio sostituisce il QuoteService dell'app con un servizio fornito dall'app di test, denominato TestQuoteService:The mock service replaces the app's QuoteService with a service provided by the test app, called TestQuoteService:

IntegrationTests.IndexPageTests.cs:IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices viene chiamato e il servizio con ambito viene registrato:ConfigureTestServices is called, and the scoped service is registered:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}

Il markup prodotto durante l'esecuzione del test riflette il testo tra virgolette fornito da TestQuoteService, quindi l'asserzione passa:The markup produced during the test's execution reflects the quote text supplied by TestQuoteService, thus the assertion passes:

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Autenticazione fittiziaMock authentication

I test nella classe AuthTests verificano che un endpoint protetto:Tests in the AuthTests class check that a secure endpoint:

  • Reindirizza un utente non autenticato alla pagina di accesso dell'app.Redirects an unauthenticated user to the app's Login page.
  • Restituisce il contenuto per un utente autenticato.Returns content for an authenticated user.

In SUT la pagina /SecurePage usa una convenzione AuthorizePage per applicare un AuthorizeFilter alla pagina.In the SUT, the /SecurePage page uses an AuthorizePage convention to apply an AuthorizeFilter to the page. Per ulteriori informazioni, vedere Razor Pages convenzioni di autorizzazione.For more information, see Razor Pages authorization conventions.

services.AddRazorPages()
    .AddRazorPagesOptions(options =>
    {
        options.Conventions.AuthorizePage("/SecurePage");
    });

Nel test Get_SecurePageRedirectsAnUnauthenticatedUser, un WebApplicationFactoryClientOptions è impostato in modo da impedire i reindirizzamenti impostando AllowAutoRedirect su false:In the Get_SecurePageRedirectsAnUnauthenticatedUser test, a WebApplicationFactoryClientOptions is set to disallow redirects by setting AllowAutoRedirect to false:

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login", 
        response.Headers.Location.OriginalString);
}

Se si impedisce al client di seguire il reindirizzamento, è possibile effettuare i controlli seguenti:By disallowing the client to follow the redirect, the following checks can be made:

  • Il codice di stato restituito da SUT può essere controllato rispetto al risultato HttpStatusCode. Redirect previsto, non al codice di stato finale dopo il reindirizzamento alla pagina di accesso, che sarebbe HttpStatusCode. OK.The status code returned by the SUT can be checked against the expected HttpStatusCode.Redirect result, not the final status code after the redirect to the Login page, which would be HttpStatusCode.OK.
  • Il valore dell'intestazione Location nelle intestazioni della risposta viene controllato per confermare che inizia con http://localhost/Identity/Account/Login, non con la risposta finale della pagina di accesso, in cui l'intestazione del Location non è presente.The Location header value in the response headers is checked to confirm that it starts with http://localhost/Identity/Account/Login, not the final Login page response, where the Location header wouldn't be present.

L'app di test può simulare un AuthenticationHandler<TOptions> in ConfigureTestServices per testare gli aspetti dell'autenticazione e dell'autorizzazione.The test app can mock an AuthenticationHandler<TOptions> in ConfigureTestServices in order to test aspects of authentication and authorization. Uno scenario minimo restituisce AuthenticateResult. Success:A minimal scenario returns an AuthenticateResult.Success:

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, 
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "Test");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

Il TestAuthHandler viene chiamato per autenticare un utente quando lo schema di autenticazione è impostato su Test dove AddAuthentication è registrato per ConfigureTestServices:The TestAuthHandler is called to authenticate a user when the authentication scheme is set to Test where AddAuthentication is registered for ConfigureTestServices:

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication("Test")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "Test", options => {});
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Test");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

Per ulteriori informazioni su WebApplicationFactoryClientOptions, vedere la sezione Opzioni client .For more information on WebApplicationFactoryClientOptions, see the Client options section.

Impostare l'ambienteSet the environment

Per impostazione predefinita, l'host e l'ambiente app di SUT sono configurati per l'utilizzo dell'ambiente di sviluppo.By default, the SUT's host and app environment is configured to use the Development environment. Per eseguire l'override dell'ambiente di SUT:To override the SUT's environment:

  • Impostare la variabile di ambiente ASPNETCORE_ENVIRONMENT (ad esempio, Staging, Productiono un altro valore personalizzato, ad esempio Testing).Set the ASPNETCORE_ENVIRONMENT environment variable (for example, Staging, Production, or other custom value, such as Testing).
  • Eseguire l'override CreateHostBuilder nell'app di test per leggere le variabili di ambiente con prefisso ASPNETCORE.Override CreateHostBuilder in the test app to read environment variables prefixed with ASPNETCORE.
protected override IHostBuilder CreateHostBuilder() => 
    base.CreateHostBuilder()
        .ConfigureHostConfiguration(
            config => config.AddEnvironmentVariables("ASPNETCORE"));

Come l'infrastruttura di test deduce il percorso radice del contenuto dell'appHow the test infrastructure infers the app content root path

Il costruttore WebApplicationFactory deduce il percorso radice del contenuto dell'app cercando un WebApplicationFactoryContentRootAttribute nell'assembly contenente i test di integrazione con una chiave uguale all'assembly TEntryPoint System.Reflection.Assembly.FullName.The WebApplicationFactory constructor infers the app content root path by searching for a WebApplicationFactoryContentRootAttribute on the assembly containing the integration tests with a key equal to the TEntryPoint assembly System.Reflection.Assembly.FullName. Se non viene trovato un attributo con la chiave corretta, WebApplicationFactory esegue il fallback alla ricerca di un file di soluzione (conestensione sln) e aggiunge il nome dell'assembly TEntryPoint alla directory della soluzione.In case an attribute with the correct key isn't found, WebApplicationFactory falls back to searching for a solution file (.sln) and appends the TEntryPoint assembly name to the solution directory. La directory radice dell'app (percorso radice del contenuto) viene usata per individuare le visualizzazioni e i file di contenuto.The app root directory (the content root path) is used to discover views and content files.

Disabilitare la copia shadowDisable shadow copying

La copia shadow comporta l'esecuzione dei test in una directory diversa rispetto alla directory di output.Shadow copying causes the tests to execute in a different directory than the output directory. Per il corretto funzionamento dei test, è necessario disabilitare la copia shadow.For tests to work properly, shadow copying must be disabled. L' app di esempio USA xUnit e Disabilita la copia shadow per xUnit includendo un file xUnit. Runner. JSON con l'impostazione di configurazione corretta.The sample app uses xUnit and disables shadow copying for xUnit by including an xunit.runner.json file with the correct configuration setting. Per altre informazioni, vedere configurazione di xUnit con JSON.For more information, see Configuring xUnit with JSON.

Aggiungere il file xUnit. Runner. JSON alla radice del progetto di test con il contenuto seguente:Add the xunit.runner.json file to root of the test project with the following content:

{
  "shadowCopy": false
}

Eliminazione di oggettiDisposal of objects

Una volta eseguiti i test dell'implementazione di IClassFixture, TestServer e HttpClient vengono eliminati quando xUnit Elimina WebApplicationFactory.After the tests of the IClassFixture implementation are executed, TestServer and HttpClient are disposed when xUnit disposes of the WebApplicationFactory. Se gli oggetti di cui è stata creata un'istanza dallo sviluppatore richiedono l'eliminazione, eliminarli nell'implementazione del IClassFixture.If objects instantiated by the developer require disposal, dispose of them in the IClassFixture implementation. Per ulteriori informazioni, vedere implementazione di un metodo Dispose.For more information, see Implementing a Dispose method.

Esempio di test di integrazioneIntegration tests sample

L' app di esempio è costituita da due app:The sample app is composed of two apps:

AppApp Directory del progettoProject directory DescrizioneDescription
App Message (SUT)Message app (the SUT) src/RazorPagesProjectsrc/RazorPagesProject Consente a un utente di aggiungere, eliminare, eliminare tutti i messaggi e analizzarli.Allows a user to add, delete one, delete all, and analyze messages.
Testare l'appTest app tests/RazorPagesProject.Teststests/RazorPagesProject.Tests Utilizzato per eseguire il test di integrazione di SUT.Used to integration test the SUT.

I test possono essere eseguiti usando le funzionalità di test predefinite di un IDE, ad esempio Visual Studio.The tests can be run using the built-in test features of an IDE, such as Visual Studio. Se si usa Visual Studio Code o la riga di comando, eseguire il comando seguente al prompt dei comandi nella directory tests/RazorPagesProject. tests :If using Visual Studio Code or the command line, execute the following command at a command prompt in the tests/RazorPagesProject.Tests directory:

dotnet test

Organizzazione Message app (SUT)Message app (SUT) organization

SUT è un sistema di messaggi di Razor Pages con le seguenti caratteristiche:The SUT is a Razor Pages message system with the following characteristics:

  • La pagina di indice dell'app (pages/index. cshtml e pages/index. cshtml. cs) fornisce i metodi di interfaccia utente e modello di pagina per controllare l'aggiunta, l'eliminazione e l'analisi dei messaggi (media parole per messaggio).The Index page of the app (Pages/Index.cshtml and Pages/Index.cshtml.cs) provides a UI and page model methods to control the addition, deletion, and analysis of messages (average words per message).
  • Un messaggio viene descritto dalla classe Message (Data/Message. cs) con due proprietà: Id (chiave) e Text (messaggio).A message is described by the Message class (Data/Message.cs) with two properties: Id (key) and Text (message). La proprietà Text è obbligatoria e limitata a 200 caratteri.The Text property is required and limited to 200 characters.
  • I messaggi vengono archiviati utilizzando† il database in memoria di Entity Framework.Messages are stored using Entity Framework's in-memory database†.
  • L'app contiene un livello di accesso ai dati (DAL) nella classe del contesto di database, AppDbContext (Data/AppDbContext. cs).The app contains a data access layer (DAL) in its database context class, AppDbContext (Data/AppDbContext.cs).
  • Se il database è vuoto all'avvio dell'app, l'archivio messaggi viene inizializzato con tre messaggi.If the database is empty on app startup, the message store is initialized with three messages.
  • L'app include un /SecurePage a cui è possibile accedere solo da un utente autenticato.The app includes a /SecurePage that can only be accessed by an authenticated user.

†L'argomento EF, test con InMemory, spiega come usare un database in memoria per i test con MSTest.†The EF topic, Test with InMemory, explains how to use an in-memory database for tests with MSTest. In questo argomento viene usato il Framework di test di xUnit .This topic uses the xUnit test framework. I concetti di test e le implementazioni di test in diversi framework di test sono simili, ma non identici.Test concepts and test implementations across different test frameworks are similar but not identical.

Anche se l'app non usa il modello di repository e non è un esempio efficace del modello di unità di lavoro (UOW), Razor Pages supporta questi modelli di sviluppo.Although the app doesn't use the repository pattern and isn't an effective example of the Unit of Work (UoW) pattern, Razor Pages supports these patterns of development. Per altre informazioni, vedere progettazione del livello di persistenza dell'infrastruttura e della logica del controller di test (l'esempio implementa il modello di repository).For more information, see Designing the infrastructure persistence layer and Test controller logic (the sample implements the repository pattern).

Testare l'organizzazione dell'appTest app organization

L'app di test è un'app console all'interno della directory tests/RazorPagesProject. tests .The test app is a console app inside the tests/RazorPagesProject.Tests directory.

Testare la directory dell'appTest app directory DescrizioneDescription
AuthTestsAuthTests Contiene i metodi di test per:Contains test methods for:
  • Accesso a una pagina sicura da un utente non autenticato.Accessing a secure page by an unauthenticated user.
  • Accesso a una pagina sicura da un utente autenticato con un AuthenticationHandler<TOptions>fittizio.Accessing a secure page by an authenticated user with a mock AuthenticationHandler<TOptions>.
  • Ottenere un profilo utente GitHub e controllare l'accesso dell'utente del profilo.Obtaining a GitHub user profile and checking the profile's user login.
BasicTestsBasicTests Contiene un metodo di test per il routing e il tipo di contenuto.Contains a test method for routing and content type.
IntegrationTestsIntegrationTests Contiene i test di integrazione per la pagina di indice utilizzando la classe WebApplicationFactory personalizzata.Contains the integration tests for the Index page using custom WebApplicationFactory class.
Helper/utilitàHelpers/Utilities
  • Utilities.cs contiene il metodo InitializeDbForTests utilizzato per eseguire il seeding del database con dati di test.Utilities.cs contains the InitializeDbForTests method used to seed the database with test data.
  • HtmlHelpers.cs fornisce un metodo per restituire un AngleSharp IHtmlDocument per l'uso da parte dei metodi di test.HtmlHelpers.cs provides a method to return an AngleSharp IHtmlDocument for use by the test methods.
  • HttpClientExtensions.cs forniscono overload per SendAsync inviare richieste a SUT.HttpClientExtensions.cs provide overloads for SendAsync to submit requests to the SUT.

Il Framework di test è xUnit.The test framework is xUnit. I test di integrazione vengono eseguiti usando Microsoft. AspNetCore. TestHost, che include TestServer.Integration tests are conducted using the Microsoft.AspNetCore.TestHost, which includes the TestServer. Poiché il pacchetto Microsoft. AspNetCore. Mvc. testing viene usato per configurare l'host di test e il server di prova, i pacchetti TestHost e TestServer non richiedono riferimenti diretti ai pacchetti nel file di progetto dell'app di test o nella configurazione per sviluppatori nell'app di test.Because the Microsoft.AspNetCore.Mvc.Testing package is used to configure the test host and test server, the TestHost and TestServer packages don't require direct package references in the test app's project file or developer configuration in the test app.

Seeding del database per il testSeeding the database for testing

I test di integrazione richiedono in genere un set di dati di piccole dimensioni nel database prima dell'esecuzione del test.Integration tests usually require a small dataset in the database prior to the test execution. Ad esempio, un test di eliminazione richiede l'eliminazione di un record del database, pertanto è necessario che il database disponga di almeno un record affinché la richiesta di eliminazione abbia esito positivo.For example, a delete test calls for a database record deletion, so the database must have at least one record for the delete request to succeed.

L'app di esempio esegue il seeding del database con tre messaggi in Utilities.cs che possono essere usati dai test quando vengono eseguiti:The sample app seeds the database with three messages in Utilities.cs that tests can use when they execute:

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

Il contesto di database di SUT viene registrato nel metodo Startup.ConfigureServices.The SUT's database context is registered in its Startup.ConfigureServices method. Il callback builder.ConfigureServices dell'app di test viene eseguito dopo l'esecuzione del codice Startup.ConfigureServices dell'app.The test app's builder.ConfigureServices callback is executed after the app's Startup.ConfigureServices code is executed. Per usare un database diverso per i test, il contesto di database dell'app deve essere sostituito in builder.ConfigureServices.To use a different database for the tests, the app's database context must be replaced in builder.ConfigureServices. Per ulteriori informazioni, vedere la sezione Customize WebApplicationFactory .For more information, see the Customize WebApplicationFactory section.

I test di integrazione assicurano che i componenti di un'app funzionino correttamente a un livello che include l'infrastruttura di supporto dell'app, ad esempio il database, la file system e la rete.Integration tests ensure that an app's components function correctly at a level that includes the app's supporting infrastructure, such as the database, file system, and network. ASP.NET Core supporta i test di integrazione utilizzando un framework unit test con un host Web di prova e un server di prova in memoria.ASP.NET Core supports integration tests using a unit test framework with a test web host and an in-memory test server.

In questo argomento si presuppone una conoscenza di base degli unit test.This topic assumes a basic understanding of unit tests. Se non si ha familiarità con i concetti di test, vedere gli unit test in .NET Core e .NET standard argomento e il contenuto collegato.If unfamiliar with test concepts, see the Unit Testing in .NET Core and .NET Standard topic and its linked content.

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

L'app di esempio è un'app Razor Pages e presuppone una conoscenza di base delle Razor Pages.The sample app is a Razor Pages app and assumes a basic understanding of Razor Pages. Se non si ha familiarità con Razor Pages, vedere gli argomenti seguenti:If unfamiliar with Razor Pages, see the following topics:

Nota

Per il test di Spa è stato consigliato uno strumento come Selenium, che consente di automatizzare un browser.For testing SPAs, we recommended a tool such as Selenium, which can automate a browser.

Introduzione ai test di integrazioneIntroduction to integration tests

I test di integrazione valutano i componenti di un'app a un livello più ampio rispetto agli unit test.Integration tests evaluate an app's components on a broader level than unit tests. Gli unit test vengono utilizzati per testare i componenti software isolati, ad esempio i singoli metodi di classe.Unit tests are used to test isolated software components, such as individual class methods. I test di integrazione confermano che due o più componenti dell'app interagiscono per produrre un risultato previsto, eventualmente includendo tutti i componenti necessari per elaborare completamente una richiesta.Integration tests confirm that two or more app components work together to produce an expected result, possibly including every component required to fully process a request.

Questi test più ampi vengono usati per testare l'infrastruttura dell'app e l'intero Framework, spesso inclusi i componenti seguenti:These broader tests are used to test the app's infrastructure and whole framework, often including the following components:

  • Database diDatabase
  • File systemFile system
  • Appliance di reteNetwork appliances
  • Pipeline richiesta-rispostaRequest-response pipeline

Gli unit test utilizzano componenti fabbricati, noti come Fakes o oggetti fittizi, al posto dei componenti dell'infrastruttura.Unit tests use fabricated components, known as fakes or mock objects, in place of infrastructure components.

Diversamente dagli unit test, i test di integrazione:In contrast to unit tests, integration tests:

  • Usare i componenti effettivi usati dall'app nell'ambiente di produzione.Use the actual components that the app uses in production.
  • Richiedere più codice e l'elaborazione dei dati.Require more code and data processing.
  • Richiedere più tempo per l'esecuzione.Take longer to run.

Pertanto, limitare l'utilizzo dei test di integrazione ai più importanti scenari di infrastruttura.Therefore, limit the use of integration tests to the most important infrastructure scenarios. Se è possibile testare un comportamento utilizzando un unit test o un test di integrazione, scegliere il unit test.If a behavior can be tested using either a unit test or an integration test, choose the unit test.

Suggerimento

Non scrivere test di integrazione per ogni possibile permutazione di dati e accesso ai file con database e file System.Don't write integration tests for every possible permutation of data and file access with databases and file systems. Indipendentemente dal numero di posizioni in un'app che interagiscono con i database e i file System, un set mirato di test di integrazione di lettura, scrittura, aggiornamento ed eliminazione è in genere in grado di testare adeguatamente i componenti di database e file system.Regardless of how many places across an app interact with databases and file systems, a focused set of read, write, update, and delete integration tests are usually capable of adequately testing database and file system components. Usare gli unit test per i test di routine della logica del metodo che interagiscono con questi componenti.Use unit tests for routine tests of method logic that interact with these components. Negli unit test, l'uso di Fakes/Mocks dell'infrastruttura comporta una maggiore esecuzione dei test.In unit tests, the use of infrastructure fakes/mocks result in faster test execution.

Nota

Nelle discussioni sui test di integrazione, il progetto testato viene spesso definito sistemasottoposto a test oppure "Sut" per brevità.In discussions of integration tests, the tested project is frequently called the System Under Test, or "SUT" for short.

"SUT" viene usato in questo argomento per fare riferimento all'app ASP.NET Core testata."SUT" is used throughout this topic to refer to the tested ASP.NET Core app.

Test di integrazione di ASP.NET CoreASP.NET Core integration tests

I test di integrazione in ASP.NET Core richiedono gli elementi seguenti:Integration tests in ASP.NET Core require the following:

  • Un progetto di test viene utilizzato per contenere ed eseguire i test.A test project is used to contain and execute the tests. Il progetto di test contiene un riferimento a SUT.The test project has a reference to the SUT.
  • Il progetto di test crea un host Web di test per SUT e usa un client del server di prova per gestire le richieste e le risposte con SUT.The test project creates a test web host for the SUT and uses a test server client to handle requests and responses with the SUT.
  • Viene usato un test runner per eseguire i test e segnalare i risultati del test.A test runner is used to execute the tests and report the test results.

I test di integrazione seguono una sequenza di eventi che includono i normali passi del test Arrange, Acte Assert :Integration tests follow a sequence of events that include the usual Arrange, Act, and Assert test steps:

  1. L'host Web di SUT è configurato.The SUT's web host is configured.
  2. Viene creato un client del server di prova per inviare richieste all'app.A test server client is created to submit requests to the app.
  3. Il passaggio di disposizione del test viene eseguito: l'app di test prepara una richiesta.The Arrange test step is executed: The test app prepares a request.
  4. Viene eseguito il passaggio del test Act : il client invia la richiesta e riceve la risposta.The Act test step is executed: The client submits the request and receives the response.
  5. Viene eseguito il passaggio del test Assert : la risposta effettiva viene convalidata come superato o non superato in base a una risposta prevista .The Assert test step is executed: The actual response is validated as a pass or fail based on an expected response.
  6. Il processo continua fino a quando non vengono eseguiti tutti i test.The process continues until all of the tests are executed.
  7. Vengono restituiti i risultati del test.The test results are reported.

In genere, l'host Web di test viene configurato in modo diverso rispetto al normale host web dell'app per le esecuzioni dei test.Usually, the test web host is configured differently than the app's normal web host for the test runs. Ad esempio, è possibile usare un database diverso o impostazioni di app diverse per i test.For example, a different database or different app settings might be used for the tests.

I componenti dell'infrastruttura, ad esempio l'host Web di test e il server di prova in memoria (TestServer), vengono forniti o gestiti dal pacchetto Microsoft. AspNetCore. Mvc. testing .Infrastructure components, such as the test web host and in-memory test server (TestServer), are provided or managed by the Microsoft.AspNetCore.Mvc.Testing package. L'utilizzo di questo pacchetto semplifica la creazione e l'esecuzione di test.Use of this package streamlines test creation and execution.

Il pacchetto di Microsoft.AspNetCore.Mvc.Testing gestisce le attività seguenti:The Microsoft.AspNetCore.Mvc.Testing package handles the following tasks:

  • Copia il file delle dipendenze ( . Deps) da SUT nella directory bin del progetto di test.Copies the dependencies file (.deps) from the SUT into the test project's bin directory.
  • Imposta la radice del contenuto sulla radice del progetto di Sut in modo che i file e le pagine e le visualizzazioni statiche vengano rilevati durante l'esecuzione dei test.Sets the content root to the SUT's project root so that static files and pages/views are found when the tests are executed.
  • Fornisce la classe WebApplicationFactory per semplificare il bootstrap del SUT con TestServer.Provides the WebApplicationFactory class to streamline bootstrapping the SUT with TestServer.

Nella documentazione relativa agli unit test viene descritto come configurare un progetto di test e un test runner, oltre a istruzioni dettagliate su come eseguire i test e le indicazioni su come denominare i test e le classi di test.The unit tests documentation describes how to set up a test project and test runner, along with detailed instructions on how to run tests and recommendations for how to name tests and test classes.

Nota

Quando si crea un progetto di test per un'app, separare gli unit test dai test di integrazione in progetti diversi.When creating a test project for an app, separate the unit tests from the integration tests into different projects. Ciò consente di garantire che i componenti di test dell'infrastruttura non vengano accidentalmente inclusi negli unit test.This helps ensure that infrastructure testing components aren't accidentally included in the unit tests. La separazione dei test di unità e integrazione consente inoltre di controllare il set di test da eseguire.Separation of unit and integration tests also allows control over which set of tests are run.

Non esiste praticamente alcuna differenza tra la configurazione per i test delle app Razor Pages e delle app MVC.There's virtually no difference between the configuration for tests of Razor Pages apps and MVC apps. L'unica differenza consiste nel modo in cui i test vengono denominati.The only difference is in how the tests are named. In un'app Razor Pages, i test degli endpoint della pagina vengono in genere denominati dopo la classe del modello di pagina, ad esempio IndexPageTests per testare l'integrazione dei componenti per la pagina di indice.In a Razor Pages app, tests of page endpoints are usually named after the page model class (for example, IndexPageTests to test component integration for the Index page). In un'app MVC i test sono in genere organizzati per classi controller e denominati dopo i controller testati, ad esempio HomeControllerTests per testare l'integrazione dei componenti per il controller Home.In an MVC app, tests are usually organized by controller classes and named after the controllers they test (for example, HomeControllerTests to test component integration for the Home controller).

Test prerequisiti appTest app prerequisites

Il progetto di test deve:The test project must:

Questi prerequisiti possono essere visualizzati nell' app di esempio.These prerequisites can be seen in the sample app. Esaminare il file tests/RazorPagesProject. tests/RazorPagesProject. tests. csproj .Inspect the tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj file. L'app di esempio usa il Framework di test xUnit e la libreria del parser AngleSharp , quindi l'app di esempio fa riferimento anche a:The sample app uses the xUnit test framework and the AngleSharp parser library, so the sample app also references:

Ambiente SUTSUT environment

Se l' ambiente di Sut non è impostato, il valore predefinito dell'ambiente è Development.If the SUT's environment isn't set, the environment defaults to Development.

Test di base con WebApplicationFactory predefinitoBasic tests with the default WebApplicationFactory

WebApplicationFactory<TEntryPoint > viene usato per creare un TestServer per i test di integrazione.WebApplicationFactory<TEntryPoint> is used to create a TestServer for the integration tests. TEntryPoint è la classe del punto di ingresso di SUT, in genere la classe Startup.TEntryPoint is the entry point class of the SUT, usually the Startup class.

Le classi di test implementano una classe fixture Interface (IClassFixture) per indicare che la classe contiene test e fornisce istanze di oggetti condivisi tra i test nella classe.Test classes implement a class fixture interface (IClassFixture) to indicate the class contains tests and provide shared object instances across the tests in the class.

La classe di test seguente, BasicTests, usa il WebApplicationFactory per avviare il SUT e fornire un HttpClient a un metodo di test, Get_EndpointsReturnSuccessAndCorrectContentType.The following test class, BasicTests, uses the WebApplicationFactory to bootstrap the SUT and provide an HttpClient to a test method, Get_EndpointsReturnSuccessAndCorrectContentType. Il metodo controlla se il codice di stato della risposta ha esito positivo (codici di stato nell'intervallo 200-299) e l'intestazione del Content-Type è text/html; charset=utf-8 per diverse pagine dell'app.The method checks if the response status code is successful (status codes in the range 200-299) and the Content-Type header is text/html; charset=utf-8 for several app pages.

CreateClient crea un'istanza di HttpClient che segue automaticamente i reindirizzamenti e gestisce i cookie.CreateClient creates an instance of HttpClient that automatically follows redirects and handles cookies.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
    private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;

    public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}

Per impostazione predefinita, i cookie non essenziali non vengono conservati nelle richieste quando i criteri di consenso GDPR sono abilitati.By default, non-essential cookies aren't preserved across requests when the GDPR consent policy is enabled. Per mantenere cookie non essenziali, ad esempio quelli usati dal provider TempData, contrassegnarli come essenziali nei test.To preserve non-essential cookies, such as those used by the TempData provider, mark them as essential in your tests. Per istruzioni su come contrassegnare un cookie come essenziale, vedere cookie essenziali.For instructions on marking a cookie as essential, see Essential cookies.

Personalizzare WebApplicationFactoryCustomize WebApplicationFactory

La configurazione dell'host Web può essere creata indipendentemente dalle classi di test ereditando da WebApplicationFactory per creare una o più factory personalizzate:Web host configuration can be created independently of the test classes by inheriting from WebApplicationFactory to create one or more custom factories:

  1. Ereditare da WebApplicationFactory ed eseguire l'override di ConfigureWebHost.Inherit from WebApplicationFactory and override ConfigureWebHost. IWebHostBuilder consente la configurazione della raccolta di servizi con ConfigureServices:The IWebHostBuilder allows the configuration of the service collection with ConfigureServices:

    public class CustomWebApplicationFactory<TStartup> 
        : WebApplicationFactory<TStartup> where TStartup: class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                // Create a new service provider.
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();
    
                // Add a database context (ApplicationDbContext) using an in-memory 
                // database for testing.
                services.AddDbContext<ApplicationDbContext>(options => 
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                    options.UseInternalServiceProvider(serviceProvider);
                });
    
                // Build the service provider.
                var sp = services.BuildServiceProvider();
    
                // Create a scope to obtain a reference to the database
                // context (ApplicationDbContext).
                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
    
                    // Ensure the database is created.
                    db.Database.EnsureCreated();
    
                    try
                    {
                        // Seed the database with test data.
                        Utilities.InitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding the database. Error: {Message}", ex.Message);
                    }
                }
            });
        }
    }
    

    Il seeding del database nell' app di esempio viene eseguito dal metodo InitializeDbForTests.Database seeding in the sample app is performed by the InitializeDbForTests method. Il metodo è descritto nella sezione esempio di test di integrazione: organizzazione di app di test .The method is described in the Integration tests sample: Test app organization section.

  2. Usare la CustomWebApplicationFactory personalizzata nelle classi di test.Use the custom CustomWebApplicationFactory in test classes. Nell'esempio seguente viene usata la Factory nella classe IndexPageTests:The following example uses the factory in the IndexPageTests class:

    public class IndexPageTests : 
        IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<RazorPagesProject.Startup> 
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
                {
                    AllowAutoRedirect = false
                });
        }
    

    Il client dell'app di esempio è configurato in modo da impedire il HttpClient dai reindirizzamenti seguenti.The sample app's client is configured to prevent the HttpClient from following redirects. Come illustrato più avanti nella sezione autenticazione fittizia , questo consente ai test di verificare il risultato della prima risposta dell'app.As explained later in the Mock authentication section, this permits tests to check the result of the app's first response. La prima risposta è un reindirizzamento in molti di questi test con un'intestazione Location.The first response is a redirect in many of these tests with a Location header.

  3. Un test tipico usa i metodi di HttpClient e helper per elaborare la richiesta e la risposta:A typical test uses the HttpClient and helper methods to process the request and the response:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    

Qualsiasi richiesta POST a SUT deve soddisfare il controllo antifalsificazione creato automaticamente dal sistema antifalsificazione di protezione dei datidell'app.Any POST request to the SUT must satisfy the antiforgery check that's automatically made by the app's data protection antiforgery system. Per disporre la richiesta POST di un test, l'app di test deve:In order to arrange for a test's POST request, the test app must:

  1. Effettuare una richiesta per la pagina.Make a request for the page.
  2. Analizzare il cookie antifalsificazione e richiedere il token di convalida dalla risposta.Parse the antiforgery cookie and request validation token from the response.
  3. Eseguire la richiesta POST con il cookie antifalsificazione e il token di convalida della richiesta sul posto.Make the POST request with the antiforgery cookie and request validation token in place.

I metodi di estensione SendAsync Helper (Helper/Metodo HttpClientExtensions. cs) e il metodo helper GetDocumentAsync (Helper/htmlhelpers. cs) nell' app di esempio usano il parser AngleSharp per gestire il controllo antifalsificazione con i metodi seguenti:The SendAsync helper extension methods (Helpers/HttpClientExtensions.cs) and the GetDocumentAsync helper method (Helpers/HtmlHelpers.cs) in the sample app use the AngleSharp parser to handle the antiforgery check with the following methods:

  • GetDocumentAsync – riceve HttpResponseMessage e restituisce un IHtmlDocument.GetDocumentAsync – Receives the HttpResponseMessage and returns an IHtmlDocument. GetDocumentAsync usa una factory che prepara una risposta virtuale basata sul HttpResponseMessageoriginale.GetDocumentAsync uses a factory that prepares a virtual response based on the original HttpResponseMessage. Per ulteriori informazioni, vedere la documentazione di AngleSharp.For more information, see the AngleSharp documentation.
  • SendAsync metodi di estensione per la HttpClient comporre un HttpRequestMessage e chiamare SendAsync (HttpRequestMessage) per inviare richieste a SUT.SendAsync extension methods for the HttpClient compose an HttpRequestMessage and call SendAsync(HttpRequestMessage) to submit requests to the SUT. Gli overload per SendAsync accettano il form HTML (IHtmlFormElement) e gli elementi seguenti:Overloads for SendAsync accept the HTML form (IHtmlFormElement) and the following:
    • Pulsante Invia del modulo (IHtmlElement)Submit button of the form (IHtmlElement)
    • Raccolta di valori di form (IEnumerable<KeyValuePair<string, string>>)Form values collection (IEnumerable<KeyValuePair<string, string>>)
    • Pulsante Invia (IHtmlElement) e valori form (IEnumerable<KeyValuePair<string, string>>)Submit button (IHtmlElement) and form values (IEnumerable<KeyValuePair<string, string>>)

Nota

AngleSharp è una libreria di analisi di terze parti usata a scopo dimostrativo in questo argomento e nell'app di esempio.AngleSharp is a third-party parsing library used for demonstration purposes in this topic and the sample app. AngleSharp non è supportato o richiesto per i test di integrazione delle app ASP.NET Core.AngleSharp isn't supported or required for integration testing of ASP.NET Core apps. È possibile usare altri parser, ad esempio HTML Agility Pack (HAP).Other parsers can be used, such as the Html Agility Pack (HAP). Un altro approccio consiste nel scrivere codice per gestire direttamente il token di verifica delle richieste del sistema antifalsificazione e il cookie antifalsificazione.Another approach is to write code to handle the antiforgery system's request verification token and antiforgery cookie directly.

Personalizzare il client con WithWebHostBuilderCustomize the client with WithWebHostBuilder

Quando è richiesta una configurazione aggiuntiva in un metodo di test, WithWebHostBuilder crea un nuovo oggetto WebApplicationFactory con un IWebHostBuilder che viene ulteriormente personalizzato in base alla configurazione.When additional configuration is required within a test method, WithWebHostBuilder creates a new WebApplicationFactory with an IWebHostBuilder that is further customized by configuration.

Il metodo di test Post_DeleteMessageHandler_ReturnsRedirectToRoot dell' app di esempio illustra l'uso di WithWebHostBuilder.The Post_DeleteMessageHandler_ReturnsRedirectToRoot test method of the sample app demonstrates the use of WithWebHostBuilder. Questo test esegue l'eliminazione di un record nel database attivando un invio di form in SUT.This test performs a record delete in the database by triggering a form submission in the SUT.

Poiché un altro test nella classe IndexPageTests esegue un'operazione che elimina tutti i record nel database e può essere eseguita prima del metodo Post_DeleteMessageHandler_ReturnsRedirectToRoot, il database viene sottoposta a reseeding in questo metodo di test per assicurarsi che sia presente un record per il SUT da eliminare.Because another test in the IndexPageTests class performs an operation that deletes all of the records in the database and may run before the Post_DeleteMessageHandler_ReturnsRedirectToRoot method, the database is reseeded in this test method to ensure that a record is present for the SUT to delete. La selezione del primo pulsante Elimina del modulo messages in SUT viene simulata nella richiesta al SUT:Selecting the first delete button of the messages form in the SUT is simulated in the request to the SUT:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                var serviceProvider = services.BuildServiceProvider();

                using (var scope = serviceProvider.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices
                        .GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<IndexPageTests>>();

                    try
                    {
                        Utilities.ReinitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding " +
                            "the database with test messages. Error: {Message}" +
                            ex.Message);
                    }
                }
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}

Opzioni clientClient options

Nella tabella seguente viene illustrato il valore predefinito di WebApplicationFactoryClientOptions disponibile durante la creazione di istanze HttpClient.The following table shows the default WebApplicationFactoryClientOptions available when creating HttpClient instances.

OpzioneOption DescrizioneDescription DefaultDefault
AllowAutoRedirectAllowAutoRedirect Ottiene o imposta un valore che indica se le istanze HttpClient devono seguire automaticamente le risposte di reindirizzamento.Gets or sets whether or not HttpClient instances should automatically follow redirect responses. true
BaseAddressBaseAddress Ottiene o imposta l'indirizzo di base delle istanze di HttpClient.Gets or sets the base address of HttpClient instances. http://localhost
HandleCookiesHandleCookies Ottiene o imposta un valore che indica se le istanze di HttpClient devono gestire i cookie.Gets or sets whether HttpClient instances should handle cookies. true
MaxAutomaticRedirectionsMaxAutomaticRedirections Ottiene o imposta il numero massimo di risposte di reindirizzamento che HttpClient istanze devono seguire.Gets or sets the maximum number of redirect responses that HttpClient instances should follow. 77

Creare la classe WebApplicationFactoryClientOptions e passarla al metodo CreateClient (i valori predefiniti sono mostrati nell'esempio di codice):Create the WebApplicationFactoryClientOptions class and pass it to the CreateClient method (default values are shown in the code example):

// Default client option values are shown
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.AllowAutoRedirect = true;
clientOptions.BaseAddress = new Uri("http://localhost");
clientOptions.HandleCookies = true;
clientOptions.MaxAutomaticRedirections = 7;

_client = _factory.CreateClient(clientOptions);

Inserire i servizi fittiziInject mock services

È possibile eseguire l'override dei servizi in un test con una chiamata a ConfigureTestServices nel generatore host.Services can be overridden in a test with a call to ConfigureTestServices on the host builder. Per inserire i servizi fittizi, SUT deve avere una classe Startup con un metodo di Startup.ConfigureServices.To inject mock services, the SUT must have a Startup class with a Startup.ConfigureServices method.

L'esempio SUT include un servizio con ambito che restituisce un'offerta.The sample SUT includes a scoped service that returns a quote. La virgoletta è incorporata in un campo nascosto nella pagina di indice quando viene richiesta la pagina di indice.The quote is embedded in a hidden field on the Index page when the Index page is requested.

Services/IQuoteService.cs:Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Startup.cs:Startup.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

Quando viene eseguita l'app SUT, viene generato il markup seguente:The following markup is generated when the SUT app is run:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

Per testare il servizio e l'inserimento di virgolette in un test di integrazione, un servizio fittizio viene inserito in SUT dal test.To test the service and quote injection in an integration test, a mock service is injected into the SUT by the test. Il servizio fittizio sostituisce il QuoteService dell'app con un servizio fornito dall'app di test, denominato TestQuoteService:The mock service replaces the app's QuoteService with a service provided by the test app, called TestQuoteService:

IntegrationTests.IndexPageTests.cs:IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices viene chiamato e il servizio con ambito viene registrato:ConfigureTestServices is called, and the scoped service is registered:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}

Il markup prodotto durante l'esecuzione del test riflette il testo tra virgolette fornito da TestQuoteService, quindi l'asserzione passa:The markup produced during the test's execution reflects the quote text supplied by TestQuoteService, thus the assertion passes:

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Autenticazione fittiziaMock authentication

I test nella classe AuthTests verificano che un endpoint protetto:Tests in the AuthTests class check that a secure endpoint:

  • Reindirizza un utente non autenticato alla pagina di accesso dell'app.Redirects an unauthenticated user to the app's Login page.
  • Restituisce il contenuto per un utente autenticato.Returns content for an authenticated user.

In SUT la pagina /SecurePage usa una convenzione AuthorizePage per applicare un AuthorizeFilter alla pagina.In the SUT, the /SecurePage page uses an AuthorizePage convention to apply an AuthorizeFilter to the page. Per ulteriori informazioni, vedere Razor Pages convenzioni di autorizzazione.For more information, see Razor Pages authorization conventions.

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .AddRazorPagesOptions(options =>
    {
        options.Conventions.AuthorizePage("/SecurePage");
    });

Nel test Get_SecurePageRedirectsAnUnauthenticatedUser, un WebApplicationFactoryClientOptions è impostato in modo da impedire i reindirizzamenti impostando AllowAutoRedirect su false:In the Get_SecurePageRedirectsAnUnauthenticatedUser test, a WebApplicationFactoryClientOptions is set to disallow redirects by setting AllowAutoRedirect to false:

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login", 
        response.Headers.Location.OriginalString);
}

Se si impedisce al client di seguire il reindirizzamento, è possibile effettuare i controlli seguenti:By disallowing the client to follow the redirect, the following checks can be made:

  • Il codice di stato restituito da SUT può essere controllato rispetto al risultato HttpStatusCode. Redirect previsto, non al codice di stato finale dopo il reindirizzamento alla pagina di accesso, che sarebbe HttpStatusCode. OK.The status code returned by the SUT can be checked against the expected HttpStatusCode.Redirect result, not the final status code after the redirect to the Login page, which would be HttpStatusCode.OK.
  • Il valore dell'intestazione Location nelle intestazioni della risposta viene controllato per confermare che inizia con http://localhost/Identity/Account/Login, non con la risposta finale della pagina di accesso, in cui l'intestazione del Location non è presente.The Location header value in the response headers is checked to confirm that it starts with http://localhost/Identity/Account/Login, not the final Login page response, where the Location header wouldn't be present.

L'app di test può simulare un AuthenticationHandler<TOptions> in ConfigureTestServices per testare gli aspetti dell'autenticazione e dell'autorizzazione.The test app can mock an AuthenticationHandler<TOptions> in ConfigureTestServices in order to test aspects of authentication and authorization. Uno scenario minimo restituisce AuthenticateResult. Success:A minimal scenario returns an AuthenticateResult.Success:

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, 
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "Test");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

Il TestAuthHandler viene chiamato per autenticare un utente quando lo schema di autenticazione è impostato su Test dove AddAuthentication è registrato per ConfigureTestServices:The TestAuthHandler is called to authenticate a user when the authentication scheme is set to Test where AddAuthentication is registered for ConfigureTestServices:

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication("Test")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "Test", options => {});
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Test");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

Per ulteriori informazioni su WebApplicationFactoryClientOptions, vedere la sezione Opzioni client .For more information on WebApplicationFactoryClientOptions, see the Client options section.

Impostare l'ambienteSet the environment

Per impostazione predefinita, l'host e l'ambiente app di SUT sono configurati per l'utilizzo dell'ambiente di sviluppo.By default, the SUT's host and app environment is configured to use the Development environment. Per eseguire l'override dell'ambiente di SUT:To override the SUT's environment:

  • Impostare la variabile di ambiente ASPNETCORE_ENVIRONMENT (ad esempio, Staging, Productiono un altro valore personalizzato, ad esempio Testing).Set the ASPNETCORE_ENVIRONMENT environment variable (for example, Staging, Production, or other custom value, such as Testing).
  • Eseguire l'override CreateHostBuilder nell'app di test per leggere le variabili di ambiente con prefisso ASPNETCORE.Override CreateHostBuilder in the test app to read environment variables prefixed with ASPNETCORE.
protected override IHostBuilder CreateHostBuilder() => 
    base.CreateHostBuilder()
        .ConfigureHostConfiguration(
            config => config.AddEnvironmentVariables("ASPNETCORE"));

Come l'infrastruttura di test deduce il percorso radice del contenuto dell'appHow the test infrastructure infers the app content root path

Il costruttore WebApplicationFactory deduce il percorso radice del contenuto dell'app cercando un WebApplicationFactoryContentRootAttribute nell'assembly contenente i test di integrazione con una chiave uguale all'assembly TEntryPoint System.Reflection.Assembly.FullName.The WebApplicationFactory constructor infers the app content root path by searching for a WebApplicationFactoryContentRootAttribute on the assembly containing the integration tests with a key equal to the TEntryPoint assembly System.Reflection.Assembly.FullName. Se non viene trovato un attributo con la chiave corretta, WebApplicationFactory esegue il fallback alla ricerca di un file di soluzione (conestensione sln) e aggiunge il nome dell'assembly TEntryPoint alla directory della soluzione.In case an attribute with the correct key isn't found, WebApplicationFactory falls back to searching for a solution file (.sln) and appends the TEntryPoint assembly name to the solution directory. La directory radice dell'app (percorso radice del contenuto) viene usata per individuare le visualizzazioni e i file di contenuto.The app root directory (the content root path) is used to discover views and content files.

Disabilitare la copia shadowDisable shadow copying

La copia shadow comporta l'esecuzione dei test in una directory diversa rispetto alla directory di output.Shadow copying causes the tests to execute in a different directory than the output directory. Per il corretto funzionamento dei test, è necessario disabilitare la copia shadow.For tests to work properly, shadow copying must be disabled. L' app di esempio USA xUnit e Disabilita la copia shadow per xUnit includendo un file xUnit. Runner. JSON con l'impostazione di configurazione corretta.The sample app uses xUnit and disables shadow copying for xUnit by including an xunit.runner.json file with the correct configuration setting. Per altre informazioni, vedere configurazione di xUnit con JSON.For more information, see Configuring xUnit with JSON.

Aggiungere il file xUnit. Runner. JSON alla radice del progetto di test con il contenuto seguente:Add the xunit.runner.json file to root of the test project with the following content:

{
  "shadowCopy": false
}

Se si usa Visual Studio, impostare la proprietà copia nella directory di output del file su copia sempre.If using Visual Studio, set the file's Copy to Output Directory property to Copy always. Se non si usa Visual Studio, aggiungere una destinazione Content al file di progetto dell'app di test:If not using Visual Studio, add a Content target to the test app's project file:

<ItemGroup>
  <Content Update="xunit.runner.json">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content>
</ItemGroup>

Eliminazione di oggettiDisposal of objects

Una volta eseguiti i test dell'implementazione di IClassFixture, TestServer e HttpClient vengono eliminati quando xUnit Elimina WebApplicationFactory.After the tests of the IClassFixture implementation are executed, TestServer and HttpClient are disposed when xUnit disposes of the WebApplicationFactory. Se gli oggetti di cui è stata creata un'istanza dallo sviluppatore richiedono l'eliminazione, eliminarli nell'implementazione del IClassFixture.If objects instantiated by the developer require disposal, dispose of them in the IClassFixture implementation. Per ulteriori informazioni, vedere implementazione di un metodo Dispose.For more information, see Implementing a Dispose method.

Esempio di test di integrazioneIntegration tests sample

L' app di esempio è costituita da due app:The sample app is composed of two apps:

AppApp Directory del progettoProject directory DescrizioneDescription
App Message (SUT)Message app (the SUT) src/RazorPagesProjectsrc/RazorPagesProject Consente a un utente di aggiungere, eliminare, eliminare tutti i messaggi e analizzarli.Allows a user to add, delete one, delete all, and analyze messages.
Testare l'appTest app tests/RazorPagesProject.Teststests/RazorPagesProject.Tests Utilizzato per eseguire il test di integrazione di SUT.Used to integration test the SUT.

I test possono essere eseguiti usando le funzionalità di test predefinite di un IDE, ad esempio Visual Studio.The tests can be run using the built-in test features of an IDE, such as Visual Studio. Se si usa Visual Studio Code o la riga di comando, eseguire il comando seguente al prompt dei comandi nella directory tests/RazorPagesProject. tests :If using Visual Studio Code or the command line, execute the following command at a command prompt in the tests/RazorPagesProject.Tests directory:

dotnet test

Organizzazione Message app (SUT)Message app (SUT) organization

SUT è un sistema di messaggi di Razor Pages con le seguenti caratteristiche:The SUT is a Razor Pages message system with the following characteristics:

  • La pagina di indice dell'app (pages/index. cshtml e pages/index. cshtml. cs) fornisce i metodi di interfaccia utente e modello di pagina per controllare l'aggiunta, l'eliminazione e l'analisi dei messaggi (media parole per messaggio).The Index page of the app (Pages/Index.cshtml and Pages/Index.cshtml.cs) provides a UI and page model methods to control the addition, deletion, and analysis of messages (average words per message).
  • Un messaggio viene descritto dalla classe Message (Data/Message. cs) con due proprietà: Id (chiave) e Text (messaggio).A message is described by the Message class (Data/Message.cs) with two properties: Id (key) and Text (message). La proprietà Text è obbligatoria e limitata a 200 caratteri.The Text property is required and limited to 200 characters.
  • I messaggi vengono archiviati utilizzando† il database in memoria di Entity Framework.Messages are stored using Entity Framework's in-memory database†.
  • L'app contiene un livello di accesso ai dati (DAL) nella classe del contesto di database, AppDbContext (Data/AppDbContext. cs).The app contains a data access layer (DAL) in its database context class, AppDbContext (Data/AppDbContext.cs).
  • Se il database è vuoto all'avvio dell'app, l'archivio messaggi viene inizializzato con tre messaggi.If the database is empty on app startup, the message store is initialized with three messages.
  • L'app include un /SecurePage a cui è possibile accedere solo da un utente autenticato.The app includes a /SecurePage that can only be accessed by an authenticated user.

†L'argomento EF, test con InMemory, spiega come usare un database in memoria per i test con MSTest.†The EF topic, Test with InMemory, explains how to use an in-memory database for tests with MSTest. In questo argomento viene usato il Framework di test di xUnit .This topic uses the xUnit test framework. I concetti di test e le implementazioni di test in diversi framework di test sono simili, ma non identici.Test concepts and test implementations across different test frameworks are similar but not identical.

Anche se l'app non usa il modello di repository e non è un esempio efficace del modello di unità di lavoro (UOW), Razor Pages supporta questi modelli di sviluppo.Although the app doesn't use the repository pattern and isn't an effective example of the Unit of Work (UoW) pattern, Razor Pages supports these patterns of development. Per altre informazioni, vedere progettazione del livello di persistenza dell'infrastruttura e della logica del controller di test (l'esempio implementa il modello di repository).For more information, see Designing the infrastructure persistence layer and Test controller logic (the sample implements the repository pattern).

Testare l'organizzazione dell'appTest app organization

L'app di test è un'app console all'interno della directory tests/RazorPagesProject. tests .The test app is a console app inside the tests/RazorPagesProject.Tests directory.

Testare la directory dell'appTest app directory DescrizioneDescription
AuthTestsAuthTests Contiene i metodi di test per:Contains test methods for:
  • Accesso a una pagina sicura da un utente non autenticato.Accessing a secure page by an unauthenticated user.
  • Accesso a una pagina sicura da un utente autenticato con un AuthenticationHandler<TOptions>fittizio.Accessing a secure page by an authenticated user with a mock AuthenticationHandler<TOptions>.
  • Ottenere un profilo utente GitHub e controllare l'accesso dell'utente del profilo.Obtaining a GitHub user profile and checking the profile's user login.
BasicTestsBasicTests Contiene un metodo di test per il routing e il tipo di contenuto.Contains a test method for routing and content type.
IntegrationTestsIntegrationTests Contiene i test di integrazione per la pagina di indice utilizzando la classe WebApplicationFactory personalizzata.Contains the integration tests for the Index page using custom WebApplicationFactory class.
Helper/utilitàHelpers/Utilities
  • Utilities.cs contiene il metodo InitializeDbForTests utilizzato per eseguire il seeding del database con dati di test.Utilities.cs contains the InitializeDbForTests method used to seed the database with test data.
  • HtmlHelpers.cs fornisce un metodo per restituire un AngleSharp IHtmlDocument per l'uso da parte dei metodi di test.HtmlHelpers.cs provides a method to return an AngleSharp IHtmlDocument for use by the test methods.
  • HttpClientExtensions.cs forniscono overload per SendAsync inviare richieste a SUT.HttpClientExtensions.cs provide overloads for SendAsync to submit requests to the SUT.

Il Framework di test è xUnit.The test framework is xUnit. I test di integrazione vengono eseguiti usando Microsoft. AspNetCore. TestHost, che include TestServer.Integration tests are conducted using the Microsoft.AspNetCore.TestHost, which includes the TestServer. Poiché il pacchetto Microsoft. AspNetCore. Mvc. testing viene usato per configurare l'host di test e il server di prova, i pacchetti TestHost e TestServer non richiedono riferimenti diretti ai pacchetti nel file di progetto dell'app di test o nella configurazione per sviluppatori nell'app di test.Because the Microsoft.AspNetCore.Mvc.Testing package is used to configure the test host and test server, the TestHost and TestServer packages don't require direct package references in the test app's project file or developer configuration in the test app.

Seeding del database per il testSeeding the database for testing

I test di integrazione richiedono in genere un set di dati di piccole dimensioni nel database prima dell'esecuzione del test.Integration tests usually require a small dataset in the database prior to the test execution. Ad esempio, un test di eliminazione richiede l'eliminazione di un record del database, pertanto è necessario che il database disponga di almeno un record affinché la richiesta di eliminazione abbia esito positivo.For example, a delete test calls for a database record deletion, so the database must have at least one record for the delete request to succeed.

L'app di esempio esegue il seeding del database con tre messaggi in Utilities.cs che possono essere usati dai test quando vengono eseguiti:The sample app seeds the database with three messages in Utilities.cs that tests can use when they execute:

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

Risorse aggiuntiveAdditional resources