Pengujian integrasi di ASP.NET Core

Oleh Jos van der Til, Martin Costello, dan Javier Calvarro Nelson.

Pengujian integrasi memastikan bahwa komponen aplikasi berfungsi dengan benar pada tingkat yang mencakup infrastruktur pendukung aplikasi, seperti database, sistem file, dan jaringan. ASP.NET Core mendukung pengujian integrasi menggunakan kerangka kerja pengujian unit dengan host web pengujian dan server pengujian dalam memori.

Artikel ini mengasumsikan pemahaman dasar tentang pengujian unit. Jika tidak terbiasa dengan konsep pengujian, lihat artikel Pengujian Unit di .NET Core dan .NET Standard dan konten tertautnya.

Melihat atau mengunduh kode sampel (cara mengunduh)

Aplikasi sampel adalah Razor aplikasi Pages dan mengasumsikan pemahaman dasar tentang Razor Pages. Jika Anda tidak terbiasa dengan Razor Pages, lihat artikel berikut ini:

Untuk menguji SPAs, kami merekomendasikan alat seperti Playwright untuk .NET, yang dapat mengotomatiskan browser.

Pengantar pengujian integrasi

Pengujian integrasi mengevaluasi komponen aplikasi pada tingkat yang lebih luas daripada pengujian unit. Pengujian unit digunakan untuk menguji komponen perangkat lunak terisolasi, seperti metode kelas individual. Pengujian integrasi mengonfirmasi bahwa dua komponen aplikasi atau lebih bekerja sama untuk menghasilkan hasil yang diharapkan, mungkin termasuk setiap komponen yang diperlukan untuk memproses permintaan sepenuhnya.

Pengujian yang lebih luas ini digunakan untuk menguji infrastruktur aplikasi dan seluruh kerangka kerja, sering kali termasuk komponen berikut:

  • Database
  • Sistem file
  • Appliance jaringan
  • Alur respons permintaan

Pengujian unit menggunakan komponen fabrikasi, yang dikenal sebagai objek palsu atau tiruan, sebagai pengganti komponen infrastruktur.

Berbeda dengan pengujian unit, pengujian integrasi:

  • Gunakan komponen aktual yang digunakan aplikasi dalam produksi.
  • Memerlukan lebih banyak kode dan pemrosesan data.
  • Membutuhkan waktu lebih lama untuk dijalankan.

Oleh karena itu, batasi penggunaan pengujian integrasi ke skenario infrastruktur yang paling penting. Jika perilaku dapat diuji menggunakan pengujian unit atau pengujian integrasi, pilih pengujian unit.

Dalam diskusi pengujian integrasi, proyek yang diuji sering disebut System Under Test, atau "SUT" singkatnya. "SUT" digunakan di seluruh artikel ini untuk merujuk ke aplikasi ASP.NET Core yang sedang diuji.

Jangan menulis pengujian integrasi untuk setiap permutasi akses data dan file dengan database dan sistem file. Terlepas dari berapa banyak tempat di seluruh aplikasi berinteraksi dengan database dan sistem file, serangkaian pengujian integrasi baca, tulis, perbarui, dan hapus yang berfokus biasanya mampu menguji database dan komponen sistem file dengan memadai. Gunakan pengujian unit untuk pengujian rutin logika metode yang berinteraksi dengan komponen-komponen ini. Dalam pengujian unit, penggunaan infrastruktur palsu atau tiruan menghasilkan eksekusi pengujian yang lebih cepat.

pengujian integrasi ASP.NET Core

Pengujian integrasi di ASP.NET Core memerlukan hal berikut:

  • Proyek pengujian digunakan untuk memuat dan menjalankan pengujian. Proyek pengujian memiliki referensi ke SUT.
  • Proyek pengujian membuat host web pengujian untuk SUT dan menggunakan klien server pengujian untuk menangani permintaan dan respons dengan SUT.
  • Runner pengujian digunakan untuk menjalankan pengujian dan melaporkan hasil pengujian.

Pengujian integrasi mengikuti urutan peristiwa yang mencakup langkah-langkah pengujian Arrange, Act, dan Assert yang biasa:

  1. Host web SUT dikonfigurasi.
  2. Klien server pengujian dibuat untuk mengirimkan permintaan ke aplikasi.
  3. Langkah Atur pengujian dijalankan: Aplikasi pengujian menyiapkan permintaan.
  4. Langkah pengujian Act dijalankan: Klien mengirimkan permintaan dan menerima respons.
  5. Langkah pengujian Assert dijalankan: Respons aktual divalidasi sebagai lulus atau gagal berdasarkan respons yang diharapkan .
  6. Proses berlanjut sampai semua pengujian dijalankan.
  7. Hasil pengujian dilaporkan.

Biasanya, host web pengujian dikonfigurasi secara berbeda dari host web normal aplikasi untuk eksekusi pengujian. Misalnya, database yang berbeda atau pengaturan aplikasi yang berbeda dapat digunakan untuk pengujian.

Komponen infrastruktur, seperti host web pengujian dan server pengujian dalam memori (TestServer), disediakan atau dikelola oleh paket Microsoft.AspNetCore.Mvc.Testing . Penggunaan paket ini menyederhanakan pembuatan dan eksekusi pengujian.

Paket Microsoft.AspNetCore.Mvc.Testing menangani tugas-tugas berikut:

  • Menyalin file dependensi (.deps) dari SUT ke direktori proyek bin pengujian.
  • Mengatur akar konten ke akar proyek SUT sehingga file statis dan halaman/tampilan ditemukan saat pengujian dijalankan.
  • Menyediakan kelas WebApplicationFactory untuk menyederhanakan bootstrapping SUT dengan TestServer.

Dokumentasi pengujian unit menjelaskan cara menyiapkan proyek pengujian dan runner pengujian, bersama dengan instruksi terperinci tentang cara menjalankan pengujian dan rekomendasi tentang cara memberi nama pengujian dan kelas pengujian.

Memisahkan pengujian unit dari pengujian integrasi ke dalam proyek yang berbeda. Memisahkan pengujian:

  • Membantu memastikan bahwa komponen pengujian infrastruktur tidak secara tidak sengaja disertakan dalam pengujian unit.
  • Memungkinkan kontrol atas set pengujian mana yang dijalankan.

Hampir tidak ada perbedaan antara konfigurasi untuk pengujian Razor aplikasi Pages dan aplikasi MVC. Satu-satunya perbedaan adalah bagaimana tes diberi nama. Razor Di aplikasi Pages, pengujian titik akhir halaman biasanya dinamai sesuai dengan kelas model halaman (misalnya, IndexPageTests untuk menguji integrasi komponen untuk halaman Indeks). Dalam aplikasi MVC, pengujian biasanya diatur oleh kelas pengontrol dan dinamai sesuai pengontrol yang mereka uji (misalnya, HomeControllerTests untuk menguji integrasi komponen untuk Home pengontrol).

Menguji prasyarat aplikasi

Proyek pengujian harus:

Prasyarat ini dapat dilihat di aplikasi sampel. Periksa file tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj. Aplikasi sampel menggunakan kerangka kerja pengujian xUnit dan pustaka pengurai AngleSharp , sehingga aplikasi sampel juga mereferensikan:

Di aplikasi yang menggunakan xunit.runner.visualstudio versi 2.4.2 atau yang lebih baru, proyek pengujian harus mereferensikan Microsoft.NET.Test.Sdk paket.

Entity Framework Core juga digunakan dalam pengujian. Lihat file proyek di GitHub.

Lingkungan SUT

Jika lingkungan SUT tidak diatur, lingkungan akan default ke Pengembangan.

Pengujian dasar dengan WebApplicationFactory default

Ekspos kelas yang ditentukan Program secara implisit ke proyek pengujian dengan melakukan salah satu hal berikut:

  • Mengekspos jenis internal dari aplikasi web ke proyek pengujian. Ini dapat dilakukan dalam file proyek SUT (.csproj):

    <ItemGroup>
         <InternalsVisibleTo Include="MyTestProject" />
    </ItemGroup>
    
  • Buat kelas publik Program menggunakan deklarasi kelas parsial:

    var builder = WebApplication.CreateBuilder(args);
    // ... Configure services, routes, etc.
    app.Run();
    + public partial class Program { }
    

    Aplikasi sampel menggunakan pendekatan kelas parsialProgram.

WebApplicationFactory<TEntryPoint> digunakan untuk membuat TestServer untuk pengujian integrasi. TEntryPoint adalah kelas titik masuk SUT, biasanya Program.cs.

Kelas pengujian menerapkan antarmuka fikstur kelas (IClassFixture) untuk menunjukkan kelas berisi pengujian dan menyediakan instans objek bersama di seluruh pengujian di kelas .

Kelas pengujian berikut, BasicTests, menggunakan WebApplicationFactory untuk bootstrap SUT dan menyediakan HttpClient untuk metode pengujian, Get_EndpointsReturnSuccessAndCorrectContentType. Metode memverifikasi kode status respons berhasil (200-299) dan Content-Type header adalah text/html; charset=utf-8 untuk beberapa halaman aplikasi.

CreateClient() membuat instans HttpClient yang secara otomatis mengikuti pengalihan dan handel cookie.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public BasicTests(WebApplicationFactory<Program> 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());
    }
}

Secara default, kebijakan persetujuan Peraturan cookiePerlindungan Data Umum tidak dipertahankan di seluruh permintaan saat kebijakan persetujuan Peraturan Perlindungan Data Umum diaktifkan. Untuk mempertahankan non-esensial cookie, seperti yang digunakan oleh penyedia TempData, tandai sebagai penting dalam pengujian Anda. Untuk petunjuk tentang menandai cookie sebagai penting, lihat Esensialcookie.

AngleSharp vs Application Parts untuk pemeriksaan antiforgery

Artikel ini menggunakan pengurai AngleSharp untuk menangani pemeriksaan antiforgery dengan memuat halaman dan mengurai HTML. Untuk menguji titik akhir pengontrol dan Razor tampilan Pages pada tingkat yang lebih rendah, tanpa peduli tentang bagaimana mereka merender di browser, pertimbangkan untuk menggunakan Application Parts. Pendekatan Bagian Aplikasi menyuntikkan pengontrol atau Razor Halaman ke dalam aplikasi yang dapat digunakan untuk membuat JSpermintaan ON untuk mendapatkan nilai yang diperlukan. Untuk informasi selengkapnya, lihat blog Pengujian Integrasi ASP.NET Sumber Daya Inti Dilindungi dengan Antiforgery Menggunakan Bagian Aplikasi dan repositori GitHub terkait oleh Martin Costello.

Menyesuaikan WebApplicationFactory

Konfigurasi host web dapat dibuat secara independen dari kelas pengujian dengan mewarisi dari WebApplicationFactory<TEntryPoint> untuk membuat satu atau beberapa pabrik kustom:

  1. Warisi dari WebApplicationFactory dan ambil alih ConfigureWebHost. memungkinkan IWebHostBuilder konfigurasi pengumpulan layanan dengan IWebHostBuilder.ConfigureServices

    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbContextOptions<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    

    Penyemaian database di aplikasi sampel dilakukan dengan InitializeDbForTests metode . Metode ini dijelaskan di bagian Sampel pengujian integrasi: Menguji organisasi aplikasi.

    Konteks database SUT terdaftar di Program.cs. Panggilan balik aplikasi builder.ConfigureServices pengujian dijalankan setelah kode aplikasi Program.cs dijalankan. Untuk menggunakan database yang berbeda untuk pengujian daripada database aplikasi, konteks database aplikasi harus diganti di builder.ConfigureServices.

    Aplikasi sampel menemukan pendeskripsi layanan untuk konteks database dan menggunakan deskriptor untuk menghapus pendaftaran layanan. Pabrik kemudian menambahkan baru ApplicationDbContext yang menggunakan database dalam memori untuk pengujian.

    Untuk menyambungkan ke database lain, ubah DbConnection. Untuk menggunakan database pengujian SQL Server:

  1. Gunakan kustom CustomWebApplicationFactory dalam kelas pengujian. Contoh berikut menggunakan pabrik di IndexPageTests kelas :

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

    Klien aplikasi sampel dikonfigurasi untuk mencegah HttpClient pengalihan berikut. Seperti yang dijelaskan kemudian di bagian autentikasi Mock, ini mengizinkan pengujian untuk memeriksa hasil respons pertama aplikasi. Respons pertama adalah pengalihan di banyak pengujian ini dengan Location header.

  2. Pengujian umum menggunakan metode dan pembantu HttpClient untuk memproses permintaan dan respons:

    [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);
    }
    

Setiap permintaan POST ke SUT harus memenuhi pemeriksaan antiforgery yang secara otomatis dibuat oleh sistem antiforgery perlindungan data aplikasi. Untuk mengatur permintaan POST pengujian, aplikasi pengujian harus:

  1. Buat permintaan untuk halaman tersebut.
  2. Uraikan antiforgery cookie dan minta token validasi dari respons.
  3. Buat permintaan POST dengan antiforgery cookie dan minta token validasi di tempat.

Metode SendAsync ekstensi pembantu (Helpers/HttpClientExtensions.cs) dan GetDocumentAsync metode pembantu (Helpers/HtmlHelpers.cs) dalam aplikasi sampel menggunakan pengurai AngleSharp untuk menangani pemeriksaan antiforgery dengan metode berikut:

  • GetDocumentAsync: Menerima HttpResponseMessage dan mengembalikan IHtmlDocument. GetDocumentAsync menggunakan pabrik yang menyiapkan respons virtual berdasarkan aslinya HttpResponseMessage. Untuk informasi selengkapnya, lihat dokumentasi AngleSharp.
  • SendAsync metode ekstensi untuk HttpClient membuat dan HttpRequestMessage memanggil SendAsync(HttpRequestMessage) untuk mengirimkan permintaan ke SUT. Kelebihan beban untuk SendAsync menerima formulir HTML (IHtmlFormElement) dan yang berikut:
    • Tombol Kirim formulir (IHtmlElement)
    • Kumpulan nilai formulir (IEnumerable<KeyValuePair<string, string>>)
    • Tombol Kirim (IHtmlElement) dan nilai formulir (IEnumerable<KeyValuePair<string, string>>)

AngleSharp adalah pustaka penguraian pihak ketiga yang digunakan untuk tujuan demonstrasi dalam artikel ini dan aplikasi sampel. AngleSharp tidak didukung atau diperlukan untuk pengujian integrasi aplikasi ASP.NET Core. Pengurai lain dapat digunakan, seperti Html Agility Pack (HAP). Pendekatan lain adalah menulis kode untuk menangani token verifikasi permintaan sistem antiforgery dan antiforgery cookie secara langsung. Lihat pemeriksaan AngleSharp vs Application Parts untuk antiforgery di artikel ini untuk informasi selengkapnya.

Penyedia database dalam memori EF-Core dapat digunakan untuk pengujian terbatas dan dasar, namun penyedia SQLite adalah pilihan yang direkomendasikan untuk pengujian dalam memori.

Lihat Memperluas Startup dengan filter startup yang menunjukkan cara mengonfigurasi middleware menggunakan IStartupFilter, yang berguna saat pengujian memerlukan layanan kustom atau middleware.

Menyesuaikan klien dengan WithWebHostBuilder

Ketika konfigurasi tambahan diperlukan dalam metode pengujian, WithWebHostBuilder buat yang baru WebApplicationFactory dengan IWebHostBuilder yang disesuaikan lebih lanjut menurut konfigurasi.

Kode sampel memanggil untuk mengganti layanan yang WithWebHostBuilder dikonfigurasi dengan stub pengujian. Untuk informasi selengkapnya dan contoh penggunaan, lihat Menyuntikkan layanan tiruan di artikel ini.

Metode Post_DeleteMessageHandler_ReturnsRedirectToRootpengujian aplikasi sampel menunjukkan penggunaan WithWebHostBuilder. Pengujian ini melakukan penghapusan rekaman dalam database dengan memicu pengiriman formulir di SUT.

Karena pengujian lain di IndexPageTests kelas melakukan operasi yang menghapus semua rekaman dalam database dan dapat berjalan sebelum Post_DeleteMessageHandler_ReturnsRedirectToRoot metode, database disembunyikan ulang dalam metode pengujian ini untuk memastikan bahwa rekaman ada untuk dihapus oleh SUT. Memilih tombol messages hapus pertama formulir di SUT disimulasikan dalam permintaan ke SUT:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    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);
}

Opsi klien

WebApplicationFactoryClientOptions Lihat halaman untuk default dan opsi yang tersedia saat membuat HttpClient instans.

WebApplicationFactoryClientOptions Buat kelas dan teruskan ke CreateClient() metode :

public class IndexPageTests :
    IClassFixture<CustomWebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<Program>
        _factory;

    public IndexPageTests(
        CustomWebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }

CATATAN: Untuk menghindari peringatan pengalihan HTTPS dalam log saat menggunakan Middleware Pengalihan HTTPS, atur BaseAddress = new Uri("https://localhost")

Menyuntikkan layanan tiruan

Layanan dapat ditimpa dalam pengujian dengan panggilan ke ConfigureTestServices pada pembuat host. Untuk mencakup layanan yang ditimpa untuk pengujian itu WithWebHostBuilder sendiri, metode ini digunakan untuk mengambil pembuat host. Ini dapat dilihat dalam pengujian berikut:

Sampel SUT menyertakan layanan terlingkup yang mengembalikan kuotasi. Kuotasi disematkan di bidang tersembunyi pada halaman Indeks saat halaman Indeks diminta.

Services/IQuoteService.cs:

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

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.");
    }
}

Program.cs:

services.AddScoped<IQuoteService, QuoteService>();

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:

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

Markup berikut dihasilkan saat aplikasi SUT dijalankan:

<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.">

Untuk menguji layanan dan mengutip injeksi dalam pengujian integrasi, layanan tiruan disuntikkan ke SUT oleh pengujian. Layanan tiruan mengganti aplikasi QuoteService dengan layanan yang disediakan oleh aplikasi pengujian, yang disebut TestQuoteService:

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(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices dipanggil, dan layanan terlingkup terdaftar:

[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);
}

Markup yang dihasilkan selama eksekusi pengujian mencerminkan teks kutipan yang disediakan oleh TestQuoteService, sehingga pernyataan lolos:

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

Autentikasi tiruan

Pengujian di AuthTests kelas memeriksa apakah titik akhir yang aman:

  • Mengalihkan pengguna yang tidak diautentikasi ke halaman masuk aplikasi.
  • Mengembalikan konten untuk pengguna yang diautentikasi.

Di SUT, /SecurePage halaman menggunakan AuthorizePage konvensi untuk menerapkan AuthorizeFilter ke halaman. Untuk informasi selengkapnya, lihat Razor Konvensi otorisasi halaman.

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

Get_SecurePageRedirectsAnUnauthenticatedUser Dalam pengujian, diatur WebApplicationFactoryClientOptions untuk melarang pengalihan dengan mengatur AllowAutoRedirect ke 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);
}

Dengan melarang klien untuk mengikuti pengalihan, pemeriksaan berikut dapat dilakukan:

  • Kode status yang dikembalikan oleh SUT dapat diperiksa terhadap hasil yang diharapkan HttpStatusCode.Redirect , bukan kode status akhir setelah pengalihan ke halaman masuk, yang akan menjadi HttpStatusCode.OK.
  • Nilai Location header di header respons dicentang untuk mengonfirmasi bahwa header dimulai dengan http://localhost/Identity/Account/Login, bukan respons halaman masuk akhir, di mana Location header tidak akan ada.

Aplikasi pengujian dapat meniru AuthenticationHandler<TOptions>ConfigureTestServices untuk menguji aspek autentikasi dan otorisasi. Skenario minimal mengembalikan AuthenticateResult.Success:

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

    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, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

dipanggil TestAuthHandler untuk mengautentikasi pengguna ketika skema autentikasi diatur ke TestScheme tempat AddAuthentication terdaftar untuk ConfigureTestServices. Penting bagi TestScheme skema untuk mencocokkan skema yang diharapkan aplikasi Anda. Jika tidak, autentikasi tidak akan berfungsi.

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

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

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

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

Untuk informasi selengkapnya tentang WebApplicationFactoryClientOptions, lihat bagian Opsi klien.

Pengujian dasar untuk middleware autentikasi

Lihat repositori GitHub ini untuk pengujian dasar middleware autentikasi. Ini berisi server pengujian yang khusus untuk skenario pengujian.

Mengatur lingkungan

Atur lingkungan di pabrik aplikasi kustom:

public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbContextOptions<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}

Cara infrastruktur pengujian menyimpulkan jalur akar konten aplikasi

WebApplicationFactory Konstruktor menyimpulkan jalur akar konten aplikasi dengan mencari pada rakitan yang WebApplicationFactoryContentRootAttribute berisi pengujian integrasi dengan kunci yang sama dengan TEntryPoint rakitan System.Reflection.Assembly.FullName. Jika atribut dengan kunci yang benar tidak ditemukan, WebApplicationFactory kembali mencari file solusi (.sln) dan menambahkan nama perakitan TEntryPoint ke direktori solusi. Direktori akar aplikasi (jalur akar konten) digunakan untuk menemukan tampilan dan file konten.

Menonaktifkan penyalinan bayangan

Penyalinan bayangan menyebabkan pengujian dijalankan di direktori yang berbeda dari direktori output. Jika pengujian Anda mengandalkan pemuatan file yang relatif terhadap Assembly.Location dan Anda mengalami masalah, Anda mungkin harus menonaktifkan penyalinan bayangan.

Untuk menonaktifkan penyalinan bayangan saat menggunakan xUnit, buat xunit.runner.json file di direktori proyek pengujian Anda, dengan pengaturan konfigurasi yang benar:

{
  "shadowCopy": false
}

Pembuangan objek

Setelah pengujian IClassFixture implementasi dijalankan, TestServer dan HttpClient dibuang ketika xUnit membuang WebApplicationFactory. Jika objek yang dibuat oleh pengembang memerlukan pembuangan, buang objek tersebut IClassFixture dalam implementasi. Untuk informasi selengkapnya, lihat Menerapkan metode Buang.

Sampel pengujian integrasi

Aplikasi sampel terdiri dari dua aplikasi:

App Direktori proyek Deskripsi
Aplikasi pesan (SUT) src/RazorPagesProject Memungkinkan pengguna untuk menambahkan, menghapus satu, menghapus semua, dan menganalisis pesan.
Menguji aplikasi tests/RazorPagesProject.Tests Digunakan untuk menguji integrasi SUT.

Pengujian dapat dijalankan menggunakan fitur pengujian bawaan IDE, seperti Visual Studio. Jika menggunakan Visual Studio Code atau baris perintah, jalankan perintah berikut pada prompt perintah di tests/RazorPagesProject.Tests direktori:

dotnet test

Organisasi aplikasi pesan (SUT)

SUT adalah Razor sistem pesan Pages dengan karakteristik berikut:

  • Halaman Indeks aplikasi (Pages/Index.cshtml dan Pages/Index.cshtml.cs) menyediakan metode UI dan model halaman untuk mengontrol penambahan, penghapusan, dan analisis pesan (kata rata-rata per pesan).
  • Pesan dijelaskan oleh Message kelas (Data/Message.cs) dengan dua properti: Id (kunci) dan Text (pesan). Properti Text diperlukan dan dibatasi hingga 200 karakter.
  • Pesan disimpan menggunakan database dalam memori Entity Framework†.
  • Aplikasi ini berisi lapisan akses data (DAL) di kelas konteks databasenya, AppDbContext (Data/AppDbContext.cs).
  • Jika database kosong pada startup aplikasi, penyimpanan pesan diinisialisasi dengan tiga pesan.
  • Aplikasi ini menyertakan /SecurePage yang hanya dapat diakses oleh pengguna yang diautentikasi.

† Artikel EF, Uji dengan InMemory, menjelaskan cara menggunakan database dalam memori untuk pengujian dengan MSTest. Topik ini menggunakan kerangka kerja pengujian xUnit . Konsep pengujian dan implementasi pengujian di berbagai kerangka kerja pengujian serupa tetapi tidak identik.

Meskipun aplikasi tidak menggunakan pola repositori dan bukan contoh pola Unit kerja (UoW) yang efektif, Razor Pages mendukung pola pengembangan ini. Untuk informasi selengkapnya, lihat Merancang lapisan persistensi infrastruktur dan Logika pengontrol Pengujian (sampel mengimplementasikan pola repositori).

Menguji organisasi aplikasi

Aplikasi pengujian adalah aplikasi konsol di tests/RazorPagesProject.Tests dalam direktori.

Menguji direktori aplikasi Deskripsi
AuthTests Berisi metode pengujian untuk:
  • Mengakses halaman aman oleh pengguna yang tidak diautentikasi.
  • Mengakses halaman aman oleh pengguna terautentikasi dengan tiruan AuthenticationHandler<TOptions>.
  • Mendapatkan profil pengguna GitHub dan memeriksa login pengguna profil.
BasicTests Berisi metode pengujian untuk perutean dan jenis konten.
IntegrationTests Berisi pengujian integrasi untuk halaman Indeks menggunakan kelas kustom WebApplicationFactory .
Helpers/Utilities
  • Utilities.cs berisi metode yang InitializeDbForTests digunakan untuk menyemai database dengan data pengujian.
  • HtmlHelpers.cs menyediakan metode untuk mengembalikan AngleSharp IHtmlDocument untuk digunakan oleh metode pengujian.
  • HttpClientExtensions.cs menyediakan kelebihan beban untuk SendAsync mengirimkan permintaan ke SUT.

Kerangka kerja pengujian adalah xUnit. Pengujian integrasi dilakukan menggunakan Microsoft.AspNetCore.TestHost, yang mencakup TestServer. Microsoft.AspNetCore.Mvc.Testing Karena paket digunakan untuk mengonfigurasi host pengujian dan server pengujian, TestHost paket dan TestServer tidak memerlukan referensi paket langsung dalam file proyek aplikasi pengujian atau konfigurasi pengembang di aplikasi pengujian.

Pengujian integrasi biasanya memerlukan himpunan data kecil dalam database sebelum eksekusi pengujian. Misalnya, penghapusan panggilan pengujian untuk penghapusan rekaman database, sehingga database harus memiliki setidaknya satu catatan agar permintaan penghapusan berhasil.

Aplikasi sampel menyemai database dengan tiga pesan dalam Utilities.cs pengujian tersebut dapat digunakan saat dijalankan:

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." }
    };
}

Konteks database SUT terdaftar di Program.cs. Panggilan balik aplikasi builder.ConfigureServices pengujian dijalankan setelah kode aplikasi Program.cs dijalankan. Untuk menggunakan database yang berbeda untuk pengujian, konteks database aplikasi harus diganti di builder.ConfigureServices. Untuk informasi selengkapnya, lihat bagian Kustomisasi WebApplicationFactory .

Sumber Daya Tambahan:

Topik ini mengasumsikan pemahaman dasar tentang pengujian unit. Jika tidak terbiasa dengan konsep pengujian, lihat Pengujian Unit dalam topik .NET Core dan .NET Standard dan konten tertautnya.

Melihat atau mengunduh kode sampel (cara mengunduh)

Aplikasi sampel adalah Razor aplikasi Pages dan mengasumsikan pemahaman dasar tentang Razor Pages. Jika tidak terbiasa dengan Razor Pages, lihat topik berikut:

Catatan

Untuk menguji SPAs, kami merekomendasikan alat seperti Playwright untuk .NET, yang dapat mengotomatiskan browser.

Pengantar pengujian integrasi

Pengujian integrasi mengevaluasi komponen aplikasi pada tingkat yang lebih luas daripada pengujian unit. Pengujian unit digunakan untuk menguji komponen perangkat lunak terisolasi, seperti metode kelas individual. Pengujian integrasi mengonfirmasi bahwa dua komponen aplikasi atau lebih bekerja sama untuk menghasilkan hasil yang diharapkan, mungkin termasuk setiap komponen yang diperlukan untuk memproses permintaan sepenuhnya.

Pengujian yang lebih luas ini digunakan untuk menguji infrastruktur aplikasi dan seluruh kerangka kerja, sering kali termasuk komponen berikut:

  • Database
  • Sistem file
  • Appliance jaringan
  • Alur respons permintaan

Pengujian unit menggunakan komponen fabrikasi, yang dikenal sebagai objek palsu atau tiruan, sebagai pengganti komponen infrastruktur.

Berbeda dengan pengujian unit, pengujian integrasi:

  • Gunakan komponen aktual yang digunakan aplikasi dalam produksi.
  • Memerlukan lebih banyak kode dan pemrosesan data.
  • Membutuhkan waktu lebih lama untuk dijalankan.

Oleh karena itu, batasi penggunaan pengujian integrasi ke skenario infrastruktur yang paling penting. Jika perilaku dapat diuji menggunakan pengujian unit atau pengujian integrasi, pilih pengujian unit.

Dalam diskusi pengujian integrasi, proyek yang diuji sering disebut System Under Test, atau "SUT" singkatnya. "SUT" digunakan di seluruh artikel ini untuk merujuk ke aplikasi ASP.NET Core yang sedang diuji.

Jangan menulis pengujian integrasi untuk setiap permutasi akses data dan file dengan database dan sistem file. Terlepas dari berapa banyak tempat di seluruh aplikasi berinteraksi dengan database dan sistem file, serangkaian pengujian integrasi baca, tulis, perbarui, dan hapus yang berfokus biasanya mampu menguji database dan komponen sistem file dengan memadai. Gunakan pengujian unit untuk pengujian rutin logika metode yang berinteraksi dengan komponen-komponen ini. Dalam pengujian unit, penggunaan infrastruktur palsu atau tiruan menghasilkan eksekusi pengujian yang lebih cepat.

pengujian integrasi ASP.NET Core

Pengujian integrasi di ASP.NET Core memerlukan hal berikut:

  • Proyek pengujian digunakan untuk memuat dan menjalankan pengujian. Proyek pengujian memiliki referensi ke SUT.
  • Proyek pengujian membuat host web pengujian untuk SUT dan menggunakan klien server pengujian untuk menangani permintaan dan respons dengan SUT.
  • Runner pengujian digunakan untuk menjalankan pengujian dan melaporkan hasil pengujian.

Pengujian integrasi mengikuti urutan peristiwa yang mencakup langkah-langkah pengujian Arrange, Act, dan Assert yang biasa:

  1. Host web SUT dikonfigurasi.
  2. Klien server pengujian dibuat untuk mengirimkan permintaan ke aplikasi.
  3. Langkah Atur pengujian dijalankan: Aplikasi pengujian menyiapkan permintaan.
  4. Langkah pengujian Act dijalankan: Klien mengirimkan permintaan dan menerima respons.
  5. Langkah pengujian Assert dijalankan: Respons aktual divalidasi sebagai lulus atau gagal berdasarkan respons yang diharapkan .
  6. Proses berlanjut sampai semua pengujian dijalankan.
  7. Hasil pengujian dilaporkan.

Biasanya, host web pengujian dikonfigurasi secara berbeda dari host web normal aplikasi untuk eksekusi pengujian. Misalnya, database yang berbeda atau pengaturan aplikasi yang berbeda dapat digunakan untuk pengujian.

Komponen infrastruktur, seperti host web pengujian dan server pengujian dalam memori (TestServer), disediakan atau dikelola oleh paket Microsoft.AspNetCore.Mvc.Testing . Penggunaan paket ini menyederhanakan pembuatan dan eksekusi pengujian.

Paket Microsoft.AspNetCore.Mvc.Testing menangani tugas-tugas berikut:

  • Menyalin file dependensi (.deps) dari SUT ke direktori proyek bin pengujian.
  • Mengatur akar konten ke akar proyek SUT sehingga file statis dan halaman/tampilan ditemukan saat pengujian dijalankan.
  • Menyediakan kelas WebApplicationFactory untuk menyederhanakan bootstrapping SUT dengan TestServer.

Dokumentasi pengujian unit menjelaskan cara menyiapkan proyek pengujian dan runner pengujian, bersama dengan instruksi terperinci tentang cara menjalankan pengujian dan rekomendasi tentang cara memberi nama pengujian dan kelas pengujian.

Memisahkan pengujian unit dari pengujian integrasi ke dalam proyek yang berbeda. Memisahkan pengujian:

  • Membantu memastikan bahwa komponen pengujian infrastruktur tidak secara tidak sengaja disertakan dalam pengujian unit.
  • Memungkinkan kontrol atas set pengujian mana yang dijalankan.

Hampir tidak ada perbedaan antara konfigurasi untuk pengujian Razor aplikasi Pages dan aplikasi MVC. Satu-satunya perbedaan adalah bagaimana tes diberi nama. Razor Di aplikasi Pages, pengujian titik akhir halaman biasanya dinamai sesuai dengan kelas model halaman (misalnya, IndexPageTests untuk menguji integrasi komponen untuk halaman Indeks). Dalam aplikasi MVC, pengujian biasanya diatur oleh kelas pengontrol dan dinamai sesuai pengontrol yang mereka uji (misalnya, HomeControllerTests untuk menguji integrasi komponen untuk Home pengontrol).

Menguji prasyarat aplikasi

Proyek pengujian harus:

Prasyarat ini dapat dilihat di aplikasi sampel. Periksa file tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj. Aplikasi sampel menggunakan kerangka kerja pengujian xUnit dan pustaka pengurai AngleSharp , sehingga aplikasi sampel juga mereferensikan:

Di aplikasi yang menggunakan xunit.runner.visualstudio versi 2.4.2 atau yang lebih baru, proyek pengujian harus mereferensikan Microsoft.NET.Test.Sdk paket.

Entity Framework Core juga digunakan dalam pengujian. Referensi aplikasi:

Lingkungan SUT

Jika lingkungan SUT tidak diatur, lingkungan akan default ke Pengembangan.

Pengujian dasar dengan WebApplicationFactory default

WebApplicationFactory<TEntryPoint> digunakan untuk membuat TestServer untuk pengujian integrasi. TEntryPoint adalah kelas titik masuk SUT, biasanya Startup kelas .

Kelas pengujian menerapkan antarmuka fikstur kelas (IClassFixture) untuk menunjukkan kelas berisi pengujian dan menyediakan instans objek bersama di seluruh pengujian di kelas .

Kelas pengujian berikut, BasicTests, menggunakan WebApplicationFactory untuk bootstrap SUT dan menyediakan HttpClient untuk metode pengujian, Get_EndpointsReturnSuccessAndCorrectContentType. Metode ini memeriksa apakah kode status respons berhasil (kode status dalam rentang 200-299) dan Content-Type header adalah text/html; charset=utf-8 untuk beberapa halaman aplikasi.

CreateClient() membuat instans HttpClient yang secara otomatis mengikuti pengalihan dan handel cookie.

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());
    }
}

Secara default, non-esensial cookietidak dipertahankan di seluruh permintaan saat kebijakan persetujuan GDPR diaktifkan. Untuk mempertahankan non-esensial cookie, seperti yang digunakan oleh penyedia TempData, tandai sebagai penting dalam pengujian Anda. Untuk petunjuk tentang menandai cookie sebagai penting, lihat Esensialcookie.

Menyesuaikan WebApplicationFactory

Konfigurasi host web dapat dibuat secara independen dari kelas pengujian dengan mewarisi dari WebApplicationFactory untuk membuat satu atau beberapa pabrik kustom:

  1. Warisi dari WebApplicationFactory dan ambil alih ConfigureWebHost. IWebHostBuilder memungkinkan konfigurasi pengumpulan layanan dengan ConfigureServices:

    public class CustomWebApplicationFactory<TStartup>
        : WebApplicationFactory<TStartup> where TStartup: class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var descriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbContextOptions<ApplicationDbContext>));
    
                services.Remove(descriptor);
    
                services.AddDbContext<ApplicationDbContext>(options =>
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                });
    
                var sp = services.BuildServiceProvider();
    
                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
    
                    db.Database.EnsureCreated();
    
                    try
                    {
                        Utilities.InitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding the " +
                            "database with test messages. Error: {Message}", ex.Message);
                    }
                }
            });
        }
    }
    

    Penyemaian database di aplikasi sampel dilakukan dengan InitializeDbForTests metode . Metode ini dijelaskan di bagian Sampel pengujian integrasi: Menguji organisasi aplikasi.

    Konteks database SUT terdaftar dalam metodenya Startup.ConfigureServices . Panggilan balik aplikasi builder.ConfigureServices pengujian dijalankan setelah kode aplikasi Startup.ConfigureServices dijalankan. Urutan eksekusi adalah perubahan yang melanggar untuk Host Generik dengan rilis ASP.NET Core 3.0. Untuk menggunakan database yang berbeda untuk pengujian daripada database aplikasi, konteks database aplikasi harus diganti di builder.ConfigureServices.

    Untuk SUT yang masih menggunakan Web Host, panggilan balik aplikasi builder.ConfigureServices pengujian dijalankan sebelum kode SUT Startup.ConfigureServices . Panggilan balik aplikasi builder.ConfigureTestServices pengujian dijalankan setelahnya.

    Aplikasi sampel menemukan pendeskripsi layanan untuk konteks database dan menggunakan deskriptor untuk menghapus pendaftaran layanan. Selanjutnya, pabrik menambahkan baru ApplicationDbContext yang menggunakan database dalam memori untuk pengujian.

    Untuk menyambungkan ke database yang berbeda dari database dalam memori, ubah UseInMemoryDatabase panggilan untuk menyambungkan konteks ke database yang berbeda. Untuk menggunakan database pengujian SQL Server:

    services.AddDbContext<ApplicationDbContext>((options, context) => 
    {
        context.UseSqlServer(
            Configuration.GetConnectionString("TestingDbConnectionString"));
    });
    
  2. Gunakan kustom CustomWebApplicationFactory dalam kelas pengujian. Contoh berikut menggunakan pabrik di IndexPageTests kelas :

    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
                });
        }
    

    Klien aplikasi sampel dikonfigurasi untuk mencegah HttpClient pengalihan berikut. Seperti yang dijelaskan kemudian di bagian autentikasi Mock, ini mengizinkan pengujian untuk memeriksa hasil respons pertama aplikasi. Respons pertama adalah pengalihan di banyak pengujian ini dengan Location header.

  3. Pengujian umum menggunakan metode dan pembantu HttpClient untuk memproses permintaan dan respons:

    [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);
    }
    

Setiap permintaan POST ke SUT harus memenuhi pemeriksaan antiforgery yang secara otomatis dibuat oleh sistem antiforgery perlindungan data aplikasi. Untuk mengatur permintaan POST pengujian, aplikasi pengujian harus:

  1. Buat permintaan untuk halaman tersebut.
  2. Uraikan antiforgery cookie dan minta token validasi dari respons.
  3. Buat permintaan POST dengan antiforgery cookie dan minta token validasi di tempat.

Metode SendAsync ekstensi pembantu (Helpers/HttpClientExtensions.cs) dan GetDocumentAsync metode pembantu (Helpers/HtmlHelpers.cs) dalam aplikasi sampel menggunakan pengurai AngleSharp untuk menangani pemeriksaan antiforgery dengan metode berikut:

  • GetDocumentAsync: Menerima HttpResponseMessage dan mengembalikan IHtmlDocument. GetDocumentAsync menggunakan pabrik yang menyiapkan respons virtual berdasarkan aslinya HttpResponseMessage. Untuk informasi selengkapnya, lihat dokumentasi AngleSharp.
  • SendAsync metode ekstensi untuk HttpClient membuat dan HttpRequestMessage memanggil SendAsync(HttpRequestMessage) untuk mengirimkan permintaan ke SUT. Kelebihan beban untuk SendAsync menerima formulir HTML (IHtmlFormElement) dan yang berikut:
    • Tombol Kirim formulir (IHtmlElement)
    • Kumpulan nilai formulir (IEnumerable<KeyValuePair<string, string>>)
    • Tombol Kirim (IHtmlElement) dan nilai formulir (IEnumerable<KeyValuePair<string, string>>)

Catatan

AngleSharp adalah pustaka penguraian pihak ketiga yang digunakan untuk tujuan demonstrasi dalam topik ini dan aplikasi sampel. AngleSharp tidak didukung atau diperlukan untuk pengujian integrasi aplikasi ASP.NET Core. Pengurai lain dapat digunakan, seperti Html Agility Pack (HAP). Pendekatan lain adalah menulis kode untuk menangani token verifikasi permintaan sistem antiforgery dan antiforgery cookie secara langsung.

Catatan

Penyedia database dalam memori EF-Core dapat digunakan untuk pengujian terbatas dan dasar, namun penyedia SQLite adalah pilihan yang direkomendasikan untuk pengujian dalam memori.

Menyesuaikan klien dengan WithWebHostBuilder

Ketika konfigurasi tambahan diperlukan dalam metode pengujian, WithWebHostBuilder buat yang baru WebApplicationFactory dengan IWebHostBuilder yang disesuaikan lebih lanjut menurut konfigurasi.

Metode Post_DeleteMessageHandler_ReturnsRedirectToRootpengujian aplikasi sampel menunjukkan penggunaan WithWebHostBuilder. Pengujian ini melakukan penghapusan rekaman dalam database dengan memicu pengiriman formulir di SUT.

Karena pengujian lain di IndexPageTests kelas melakukan operasi yang menghapus semua rekaman dalam database dan dapat berjalan sebelum Post_DeleteMessageHandler_ReturnsRedirectToRoot metode, database disembunyikan ulang dalam metode pengujian ini untuk memastikan bahwa rekaman ada untuk dihapus oleh SUT. Memilih tombol messages hapus pertama formulir di SUT disimulasikan dalam permintaan ke 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);
}

Opsi klien

Tabel berikut menunjukkan default WebApplicationFactoryClientOptions yang tersedia saat membuat HttpClient instans.

Opsi Deskripsi Default
AllowAutoRedirect Mendapatkan atau mengatur apakah HttpClient instans harus secara otomatis mengikuti respons pengalihan. true
BaseAddress Mendapatkan atau mengatur alamat HttpClient dasar instans. http://localhost
HandleCookies Mendapatkan atau mengatur apakah HttpClient instans harus menangani cookies. true
MaxAutomaticRedirections Mendapatkan atau mengatur jumlah maksimum respons pengalihan yang HttpClient harus diikuti instans. 7

WebApplicationFactoryClientOptions Buat kelas dan teruskan ke CreateClient() metode (nilai default ditampilkan dalam contoh kode):

// 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);

Menyuntikkan layanan tiruan

Layanan dapat ditimpa dalam pengujian dengan panggilan ke ConfigureTestServices pada pembuat host. Untuk menyuntikkan layanan tiruan, SUT harus memiliki Startup kelas dengan Startup.ConfigureServices metode .

Sampel SUT menyertakan layanan terlingkup yang mengembalikan kuotasi. Kuotasi disematkan di bidang tersembunyi pada halaman Indeks saat halaman Indeks diminta.

Services/IQuoteService.cs:

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

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:

services.AddScoped<IQuoteService, QuoteService>();

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:

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

Markup berikut dihasilkan saat aplikasi SUT dijalankan:

<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.">

Untuk menguji layanan dan mengutip injeksi dalam pengujian integrasi, layanan tiruan disuntikkan ke SUT oleh pengujian. Layanan tiruan mengganti aplikasi QuoteService dengan layanan yang disediakan oleh aplikasi pengujian, yang disebut TestQuoteService:

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 dipanggil, dan layanan terlingkup terdaftar:

[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);
}

Markup yang dihasilkan selama eksekusi pengujian mencerminkan teks kutipan yang disediakan oleh TestQuoteService, sehingga pernyataan lolos:

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

Autentikasi tiruan

Pengujian di AuthTests kelas memeriksa apakah titik akhir yang aman:

  • Mengalihkan pengguna yang tidak diautentikasi ke halaman Masuk aplikasi.
  • Mengembalikan konten untuk pengguna yang diautentikasi.

Di SUT, /SecurePage halaman menggunakan AuthorizePage konvensi untuk menerapkan AuthorizeFilter ke halaman. Untuk informasi selengkapnya, lihat Razor Konvensi otorisasi halaman.

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

Get_SecurePageRedirectsAnUnauthenticatedUser Dalam pengujian, diatur WebApplicationFactoryClientOptions untuk melarang pengalihan dengan mengatur AllowAutoRedirect ke 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);
}

Dengan melarang klien untuk mengikuti pengalihan, pemeriksaan berikut dapat dilakukan:

  • Kode status yang dikembalikan oleh SUT dapat diperiksa terhadap hasil yang diharapkan HttpStatusCode.Redirect , bukan kode status akhir setelah pengalihan ke halaman Login, yang akan menjadi HttpStatusCode.OK.
  • Nilai Location header di header respons dicentang untuk mengonfirmasi bahwa header dimulai dengan http://localhost/Identity/Account/Login, bukan respons halaman Masuk akhir, di mana Location header tidak akan ada.

Aplikasi pengujian dapat meniru AuthenticationHandler<TOptions>ConfigureTestServices untuk menguji aspek autentikasi dan otorisasi. Skenario minimal mengembalikan 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);
    }
}

dipanggil TestAuthHandler untuk mengautentikasi pengguna ketika skema autentikasi diatur ke Test tempat AddAuthentication terdaftar untuk ConfigureTestServices. Penting bagi Test skema untuk mencocokkan skema yang diharapkan aplikasi Anda. Jika tidak, autentikasi tidak akan berfungsi.

[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);
}

Untuk informasi selengkapnya tentang WebApplicationFactoryClientOptions, lihat bagian Opsi klien.

Mengatur lingkungan

Secara default, host SUT dan lingkungan aplikasi dikonfigurasi untuk menggunakan lingkungan Pengembangan. Untuk mengambil alih lingkungan SUT saat menggunakan IHostBuilder:

  • Atur ASPNETCORE_ENVIRONMENT variabel lingkungan (misalnya, Staging, , Productionatau nilai kustom lainnya, seperti Testing).
  • Ambil alih CreateHostBuilder dalam aplikasi pengujian untuk membaca variabel lingkungan yang diawali dengan ASPNETCORE.
protected override IHostBuilder CreateHostBuilder() =>
    base.CreateHostBuilder()
        .ConfigureHostConfiguration(
            config => config.AddEnvironmentVariables("ASPNETCORE"));

Jika SUT menggunakan Web Host (IWebHostBuilder), ambil alih CreateWebHostBuilder:

protected override IWebHostBuilder CreateWebHostBuilder() =>
    base.CreateWebHostBuilder().UseEnvironment("Testing");

Cara infrastruktur pengujian menyimpulkan jalur akar konten aplikasi

WebApplicationFactory Konstruktor menyimpulkan jalur akar konten aplikasi dengan mencari pada rakitan yang WebApplicationFactoryContentRootAttribute berisi pengujian integrasi dengan kunci yang sama dengan TEntryPoint rakitan System.Reflection.Assembly.FullName. Jika atribut dengan kunci yang benar tidak ditemukan, WebApplicationFactory kembali mencari file solusi (.sln) dan menambahkan nama perakitan TEntryPoint ke direktori solusi. Direktori akar aplikasi (jalur akar konten) digunakan untuk menemukan tampilan dan file konten.

Menonaktifkan penyalinan bayangan

Penyalinan bayangan menyebabkan pengujian dijalankan di direktori yang berbeda dari direktori output. Jika pengujian Anda mengandalkan pemuatan file yang relatif terhadap Assembly.Location dan Anda mengalami masalah, Anda mungkin harus menonaktifkan penyalinan bayangan.

Untuk menonaktifkan penyalinan bayangan saat menggunakan xUnit, buat xunit.runner.json file di direktori proyek pengujian Anda, dengan pengaturan konfigurasi yang benar:

{
  "shadowCopy": false
}

Pembuangan objek

Setelah pengujian IClassFixture implementasi dijalankan, TestServer dan HttpClient dibuang ketika xUnit membuang WebApplicationFactory. Jika objek yang dibuat oleh pengembang memerlukan pembuangan, buang objek tersebut IClassFixture dalam implementasi. Untuk informasi selengkapnya, lihat Menerapkan metode Buang.

Sampel pengujian integrasi

Aplikasi sampel terdiri dari dua aplikasi:

App Direktori proyek Deskripsi
Aplikasi pesan (SUT) src/RazorPagesProject Memungkinkan pengguna untuk menambahkan, menghapus satu, menghapus semua, dan menganalisis pesan.
Menguji aplikasi tests/RazorPagesProject.Tests Digunakan untuk menguji integrasi SUT.

Pengujian dapat dijalankan menggunakan fitur pengujian bawaan IDE, seperti Visual Studio. Jika menggunakan Visual Studio Code atau baris perintah, jalankan perintah berikut pada prompt perintah di tests/RazorPagesProject.Tests direktori:

dotnet test

Organisasi aplikasi pesan (SUT)

SUT adalah Razor sistem pesan Pages dengan karakteristik berikut:

  • Halaman Indeks aplikasi (Pages/Index.cshtml dan Pages/Index.cshtml.cs) menyediakan metode UI dan model halaman untuk mengontrol penambahan, penghapusan, dan analisis pesan (kata rata-rata per pesan).
  • Pesan dijelaskan oleh Message kelas (Data/Message.cs) dengan dua properti: Id (kunci) dan Text (pesan). Properti Text diperlukan dan dibatasi hingga 200 karakter.
  • Pesan disimpan menggunakan database dalam memori Entity Framework†.
  • Aplikasi ini berisi lapisan akses data (DAL) di kelas konteks databasenya, AppDbContext (Data/AppDbContext.cs).
  • Jika database kosong pada startup aplikasi, penyimpanan pesan diinisialisasi dengan tiga pesan.
  • Aplikasi ini menyertakan /SecurePage yang hanya dapat diakses oleh pengguna yang diautentikasi.

† Topik EF, Uji dengan InMemory, menjelaskan cara menggunakan database dalam memori untuk pengujian dengan MSTest. Topik ini menggunakan kerangka kerja pengujian xUnit . Konsep pengujian dan implementasi pengujian di berbagai kerangka kerja pengujian serupa tetapi tidak identik.

Meskipun aplikasi tidak menggunakan pola repositori dan bukan contoh pola Unit kerja (UoW) yang efektif, Razor Pages mendukung pola pengembangan ini. Untuk informasi selengkapnya, lihat Merancang lapisan persistensi infrastruktur dan Logika pengontrol Pengujian (sampel mengimplementasikan pola repositori).

Menguji organisasi aplikasi

Aplikasi pengujian adalah aplikasi konsol di tests/RazorPagesProject.Tests dalam direktori.

Menguji direktori aplikasi Deskripsi
AuthTests Berisi metode pengujian untuk:
  • Mengakses halaman aman oleh pengguna yang tidak diautentikasi.
  • Mengakses halaman aman oleh pengguna terautentikasi dengan tiruan AuthenticationHandler<TOptions>.
  • Mendapatkan profil pengguna GitHub dan memeriksa login pengguna profil.
BasicTests Berisi metode pengujian untuk perutean dan jenis konten.
IntegrationTests Berisi pengujian integrasi untuk halaman Indeks menggunakan kelas kustom WebApplicationFactory .
Helpers/Utilities
  • Utilities.cs berisi metode yang InitializeDbForTests digunakan untuk menyemai database dengan data pengujian.
  • HtmlHelpers.cs menyediakan metode untuk mengembalikan AngleSharp IHtmlDocument untuk digunakan oleh metode pengujian.
  • HttpClientExtensions.cs menyediakan kelebihan beban untuk SendAsync mengirimkan permintaan ke SUT.

Kerangka kerja pengujian adalah xUnit. Pengujian integrasi dilakukan menggunakan Microsoft.AspNetCore.TestHost, yang mencakup TestServer. Microsoft.AspNetCore.Mvc.Testing Karena paket digunakan untuk mengonfigurasi host pengujian dan server pengujian, TestHost paket dan TestServer tidak memerlukan referensi paket langsung dalam file proyek aplikasi pengujian atau konfigurasi pengembang di aplikasi pengujian.

Pengujian integrasi biasanya memerlukan himpunan data kecil dalam database sebelum eksekusi pengujian. Misalnya, penghapusan panggilan pengujian untuk penghapusan rekaman database, sehingga database harus memiliki setidaknya satu catatan agar permintaan penghapusan berhasil.

Aplikasi sampel menyemai database dengan tiga pesan dalam Utilities.cs pengujian tersebut dapat digunakan saat dijalankan:

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." }
    };
}

Konteks database SUT terdaftar dalam metodenya Startup.ConfigureServices . Panggilan balik aplikasi builder.ConfigureServices pengujian dijalankan setelah kode aplikasi Startup.ConfigureServices dijalankan. Untuk menggunakan database yang berbeda untuk pengujian, konteks database aplikasi harus diganti di builder.ConfigureServices. Untuk informasi selengkapnya, lihat bagian Kustomisasi WebApplicationFactory .

Untuk SUT yang masih menggunakan Web Host, panggilan balik aplikasi builder.ConfigureServices pengujian dijalankan sebelum kode SUT Startup.ConfigureServices . Panggilan balik aplikasi builder.ConfigureTestServices pengujian dijalankan setelahnya.

Sumber Daya Tambahan:

Artikel ini mengasumsikan pemahaman dasar tentang pengujian unit. Jika tidak terbiasa dengan konsep pengujian, lihat artikel Pengujian Unit di .NET Core dan .NET Standard dan konten tertautnya.

Melihat atau mengunduh kode sampel (cara mengunduh)

Aplikasi sampel adalah Razor aplikasi Pages dan mengasumsikan pemahaman dasar tentang Razor Pages. Jika Anda tidak terbiasa dengan Razor Pages, lihat artikel berikut ini:

Untuk menguji SPAs, kami merekomendasikan alat seperti Playwright untuk .NET, yang dapat mengotomatiskan browser.

Pengantar pengujian integrasi

Pengujian integrasi mengevaluasi komponen aplikasi pada tingkat yang lebih luas daripada pengujian unit. Pengujian unit digunakan untuk menguji komponen perangkat lunak terisolasi, seperti metode kelas individual. Pengujian integrasi mengonfirmasi bahwa dua komponen aplikasi atau lebih bekerja sama untuk menghasilkan hasil yang diharapkan, mungkin termasuk setiap komponen yang diperlukan untuk memproses permintaan sepenuhnya.

Pengujian yang lebih luas ini digunakan untuk menguji infrastruktur aplikasi dan seluruh kerangka kerja, sering kali termasuk komponen berikut:

  • Database
  • Sistem file
  • Appliance jaringan
  • Alur respons permintaan

Pengujian unit menggunakan komponen fabrikasi, yang dikenal sebagai objek palsu atau tiruan, sebagai pengganti komponen infrastruktur.

Berbeda dengan pengujian unit, pengujian integrasi:

  • Gunakan komponen aktual yang digunakan aplikasi dalam produksi.
  • Memerlukan lebih banyak kode dan pemrosesan data.
  • Membutuhkan waktu lebih lama untuk dijalankan.

Oleh karena itu, batasi penggunaan pengujian integrasi ke skenario infrastruktur yang paling penting. Jika perilaku dapat diuji menggunakan pengujian unit atau pengujian integrasi, pilih pengujian unit.

Dalam diskusi pengujian integrasi, proyek yang diuji sering disebut System Under Test, atau "SUT" singkatnya. "SUT" digunakan di seluruh artikel ini untuk merujuk ke aplikasi ASP.NET Core yang sedang diuji.

Jangan menulis pengujian integrasi untuk setiap permutasi akses data dan file dengan database dan sistem file. Terlepas dari berapa banyak tempat di seluruh aplikasi berinteraksi dengan database dan sistem file, serangkaian pengujian integrasi baca, tulis, perbarui, dan hapus yang berfokus biasanya mampu menguji database dan komponen sistem file dengan memadai. Gunakan pengujian unit untuk pengujian rutin logika metode yang berinteraksi dengan komponen-komponen ini. Dalam pengujian unit, penggunaan infrastruktur palsu atau tiruan menghasilkan eksekusi pengujian yang lebih cepat.

pengujian integrasi ASP.NET Core

Pengujian integrasi di ASP.NET Core memerlukan hal berikut:

  • Proyek pengujian digunakan untuk memuat dan menjalankan pengujian. Proyek pengujian memiliki referensi ke SUT.
  • Proyek pengujian membuat host web pengujian untuk SUT dan menggunakan klien server pengujian untuk menangani permintaan dan respons dengan SUT.
  • Runner pengujian digunakan untuk menjalankan pengujian dan melaporkan hasil pengujian.

Pengujian integrasi mengikuti urutan peristiwa yang mencakup langkah-langkah pengujian Arrange, Act, dan Assert yang biasa:

  1. Host web SUT dikonfigurasi.
  2. Klien server pengujian dibuat untuk mengirimkan permintaan ke aplikasi.
  3. Langkah Atur pengujian dijalankan: Aplikasi pengujian menyiapkan permintaan.
  4. Langkah pengujian Act dijalankan: Klien mengirimkan permintaan dan menerima respons.
  5. Langkah pengujian Assert dijalankan: Respons aktual divalidasi sebagai lulus atau gagal berdasarkan respons yang diharapkan .
  6. Proses berlanjut sampai semua pengujian dijalankan.
  7. Hasil pengujian dilaporkan.

Biasanya, host web pengujian dikonfigurasi secara berbeda dari host web normal aplikasi untuk eksekusi pengujian. Misalnya, database yang berbeda atau pengaturan aplikasi yang berbeda dapat digunakan untuk pengujian.

Komponen infrastruktur, seperti host web pengujian dan server pengujian dalam memori (TestServer), disediakan atau dikelola oleh paket Microsoft.AspNetCore.Mvc.Testing . Penggunaan paket ini menyederhanakan pembuatan dan eksekusi pengujian.

Paket Microsoft.AspNetCore.Mvc.Testing menangani tugas-tugas berikut:

  • Menyalin file dependensi (.deps) dari SUT ke direktori proyek bin pengujian.
  • Mengatur akar konten ke akar proyek SUT sehingga file statis dan halaman/tampilan ditemukan saat pengujian dijalankan.
  • Menyediakan kelas WebApplicationFactory untuk menyederhanakan bootstrapping SUT dengan TestServer.

Dokumentasi pengujian unit menjelaskan cara menyiapkan proyek pengujian dan runner pengujian, bersama dengan instruksi terperinci tentang cara menjalankan pengujian dan rekomendasi tentang cara memberi nama pengujian dan kelas pengujian.

Memisahkan pengujian unit dari pengujian integrasi ke dalam proyek yang berbeda. Memisahkan pengujian:

  • Membantu memastikan bahwa komponen pengujian infrastruktur tidak secara tidak sengaja disertakan dalam pengujian unit.
  • Memungkinkan kontrol atas set pengujian mana yang dijalankan.

Hampir tidak ada perbedaan antara konfigurasi untuk pengujian Razor aplikasi Pages dan aplikasi MVC. Satu-satunya perbedaan adalah bagaimana tes diberi nama. Razor Di aplikasi Pages, pengujian titik akhir halaman biasanya dinamai sesuai dengan kelas model halaman (misalnya, IndexPageTests untuk menguji integrasi komponen untuk halaman Indeks). Dalam aplikasi MVC, pengujian biasanya diatur oleh kelas pengontrol dan dinamai sesuai pengontrol yang mereka uji (misalnya, HomeControllerTests untuk menguji integrasi komponen untuk Home pengontrol).

Menguji prasyarat aplikasi

Proyek pengujian harus:

Prasyarat ini dapat dilihat di aplikasi sampel. Periksa file tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj. Aplikasi sampel menggunakan kerangka kerja pengujian xUnit dan pustaka pengurai AngleSharp , sehingga aplikasi sampel juga mereferensikan:

Di aplikasi yang menggunakan xunit.runner.visualstudio versi 2.4.2 atau yang lebih baru, proyek pengujian harus mereferensikan Microsoft.NET.Test.Sdk paket.

Entity Framework Core juga digunakan dalam pengujian. Lihat file proyek di GitHub.

Lingkungan SUT

Jika lingkungan SUT tidak diatur, lingkungan akan default ke Pengembangan.

Pengujian dasar dengan WebApplicationFactory default

Ekspos kelas yang ditentukan Program secara implisit ke proyek pengujian dengan melakukan salah satu hal berikut:

  • Mengekspos jenis internal dari aplikasi web ke proyek pengujian. Ini dapat dilakukan dalam file proyek SUT (.csproj):

    <ItemGroup>
         <InternalsVisibleTo Include="MyTestProject" />
    </ItemGroup>
    
  • Buat kelas publik Program menggunakan deklarasi kelas parsial:

    var builder = WebApplication.CreateBuilder(args);
    // ... Configure services, routes, etc.
    app.Run();
    + public partial class Program { }
    

    Aplikasi sampel menggunakan pendekatan kelas parsialProgram.

WebApplicationFactory<TEntryPoint> digunakan untuk membuat TestServer untuk pengujian integrasi. TEntryPoint adalah kelas titik masuk SUT, biasanya Program.cs.

Kelas pengujian menerapkan antarmuka fikstur kelas (IClassFixture) untuk menunjukkan kelas berisi pengujian dan menyediakan instans objek bersama di seluruh pengujian di kelas .

Kelas pengujian berikut, BasicTests, menggunakan WebApplicationFactory untuk bootstrap SUT dan menyediakan HttpClient untuk metode pengujian, Get_EndpointsReturnSuccessAndCorrectContentType. Metode memverifikasi kode status respons berhasil (200-299) dan Content-Type header adalah text/html; charset=utf-8 untuk beberapa halaman aplikasi.

CreateClient() membuat instans HttpClient yang secara otomatis mengikuti pengalihan dan handel cookie.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public BasicTests(WebApplicationFactory<Program> 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());
    }
}

Secara default, kebijakan persetujuan Peraturan cookiePerlindungan Data Umum tidak dipertahankan di seluruh permintaan saat kebijakan persetujuan Peraturan Perlindungan Data Umum diaktifkan. Untuk mempertahankan non-esensial cookie, seperti yang digunakan oleh penyedia TempData, tandai sebagai penting dalam pengujian Anda. Untuk petunjuk tentang menandai cookie sebagai penting, lihat Esensialcookie.

AngleSharp vs Application Parts untuk pemeriksaan antiforgery

Artikel ini menggunakan pengurai AngleSharp untuk menangani pemeriksaan antiforgery dengan memuat halaman dan mengurai HTML. Untuk menguji titik akhir pengontrol dan Razor tampilan Pages pada tingkat yang lebih rendah, tanpa peduli tentang bagaimana mereka merender di browser, pertimbangkan untuk menggunakan Application Parts. Pendekatan Bagian Aplikasi menyuntikkan pengontrol atau Razor Halaman ke dalam aplikasi yang dapat digunakan untuk membuat JSpermintaan ON untuk mendapatkan nilai yang diperlukan. Untuk informasi selengkapnya, lihat blog Pengujian Integrasi ASP.NET Sumber Daya Inti Dilindungi dengan Antiforgery Menggunakan Bagian Aplikasi dan repositori GitHub terkait oleh Martin Costello.

Menyesuaikan WebApplicationFactory

Konfigurasi host web dapat dibuat secara independen dari kelas pengujian dengan mewarisi dari WebApplicationFactory<TEntryPoint> untuk membuat satu atau beberapa pabrik kustom:

  1. Warisi dari WebApplicationFactory dan ambil alih ConfigureWebHost. memungkinkan IWebHostBuilder konfigurasi pengumpulan layanan dengan IWebHostBuilder.ConfigureServices

    public class CustomWebApplicationFactory<TProgram>
        : WebApplicationFactory<TProgram> where TProgram : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var dbContextDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbContextOptions<ApplicationDbContext>));
    
                services.Remove(dbContextDescriptor);
    
                var dbConnectionDescriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbConnection));
    
                services.Remove(dbConnectionDescriptor);
    
                // Create open SqliteConnection so EF won't automatically close it.
                services.AddSingleton<DbConnection>(container =>
                {
                    var connection = new SqliteConnection("DataSource=:memory:");
                    connection.Open();
    
                    return connection;
                });
    
                services.AddDbContext<ApplicationDbContext>((container, options) =>
                {
                    var connection = container.GetRequiredService<DbConnection>();
                    options.UseSqlite(connection);
                });
            });
    
            builder.UseEnvironment("Development");
        }
    }
    

    Penyemaian database di aplikasi sampel dilakukan dengan InitializeDbForTests metode . Metode ini dijelaskan di bagian Sampel pengujian integrasi: Menguji organisasi aplikasi.

    Konteks database SUT terdaftar di Program.cs. Panggilan balik aplikasi builder.ConfigureServices pengujian dijalankan setelah kode aplikasi Program.cs dijalankan. Untuk menggunakan database yang berbeda untuk pengujian daripada database aplikasi, konteks database aplikasi harus diganti di builder.ConfigureServices.

    Aplikasi sampel menemukan pendeskripsi layanan untuk konteks database dan menggunakan deskriptor untuk menghapus pendaftaran layanan. Pabrik kemudian menambahkan baru ApplicationDbContext yang menggunakan database dalam memori untuk pengujian.

    Untuk menyambungkan ke database lain, ubah DbConnection. Untuk menggunakan database pengujian SQL Server:

  1. Gunakan kustom CustomWebApplicationFactory dalam kelas pengujian. Contoh berikut menggunakan pabrik di IndexPageTests kelas :

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

    Klien aplikasi sampel dikonfigurasi untuk mencegah HttpClient pengalihan berikut. Seperti yang dijelaskan kemudian di bagian autentikasi Mock, ini mengizinkan pengujian untuk memeriksa hasil respons pertama aplikasi. Respons pertama adalah pengalihan di banyak pengujian ini dengan Location header.

  2. Pengujian umum menggunakan metode dan pembantu HttpClient untuk memproses permintaan dan respons:

    [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);
    }
    

Setiap permintaan POST ke SUT harus memenuhi pemeriksaan antiforgery yang secara otomatis dibuat oleh sistem antiforgery perlindungan data aplikasi. Untuk mengatur permintaan POST pengujian, aplikasi pengujian harus:

  1. Buat permintaan untuk halaman tersebut.
  2. Uraikan antiforgery cookie dan minta token validasi dari respons.
  3. Buat permintaan POST dengan antiforgery cookie dan minta token validasi di tempat.

Metode SendAsync ekstensi pembantu (Helpers/HttpClientExtensions.cs) dan GetDocumentAsync metode pembantu (Helpers/HtmlHelpers.cs) dalam aplikasi sampel menggunakan pengurai AngleSharp untuk menangani pemeriksaan antiforgery dengan metode berikut:

  • GetDocumentAsync: Menerima HttpResponseMessage dan mengembalikan IHtmlDocument. GetDocumentAsync menggunakan pabrik yang menyiapkan respons virtual berdasarkan aslinya HttpResponseMessage. Untuk informasi selengkapnya, lihat dokumentasi AngleSharp.
  • SendAsync metode ekstensi untuk HttpClient membuat dan HttpRequestMessage memanggil SendAsync(HttpRequestMessage) untuk mengirimkan permintaan ke SUT. Kelebihan beban untuk SendAsync menerima formulir HTML (IHtmlFormElement) dan yang berikut:
    • Tombol Kirim formulir (IHtmlElement)
    • Kumpulan nilai formulir (IEnumerable<KeyValuePair<string, string>>)
    • Tombol Kirim (IHtmlElement) dan nilai formulir (IEnumerable<KeyValuePair<string, string>>)

AngleSharp adalah pustaka penguraian pihak ketiga yang digunakan untuk tujuan demonstrasi dalam artikel ini dan aplikasi sampel. AngleSharp tidak didukung atau diperlukan untuk pengujian integrasi aplikasi ASP.NET Core. Pengurai lain dapat digunakan, seperti Html Agility Pack (HAP). Pendekatan lain adalah menulis kode untuk menangani token verifikasi permintaan sistem antiforgery dan antiforgery cookie secara langsung. Lihat pemeriksaan AngleSharp vs Application Parts untuk antiforgery di artikel ini untuk informasi selengkapnya.

Penyedia database dalam memori EF-Core dapat digunakan untuk pengujian terbatas dan dasar, namun penyedia SQLite adalah pilihan yang direkomendasikan untuk pengujian dalam memori.

Lihat Memperluas Startup dengan filter startup yang menunjukkan cara mengonfigurasi middleware menggunakan IStartupFilter, yang berguna saat pengujian memerlukan layanan kustom atau middleware.

Menyesuaikan klien dengan WithWebHostBuilder

Ketika konfigurasi tambahan diperlukan dalam metode pengujian, WithWebHostBuilder buat yang baru WebApplicationFactory dengan IWebHostBuilder yang disesuaikan lebih lanjut menurut konfigurasi.

Kode sampel memanggil untuk mengganti layanan yang WithWebHostBuilder dikonfigurasi dengan stub pengujian. Untuk informasi selengkapnya dan contoh penggunaan, lihat Menyuntikkan layanan tiruan di artikel ini.

Metode Post_DeleteMessageHandler_ReturnsRedirectToRootpengujian aplikasi sampel menunjukkan penggunaan WithWebHostBuilder. Pengujian ini melakukan penghapusan rekaman dalam database dengan memicu pengiriman formulir di SUT.

Karena pengujian lain di IndexPageTests kelas melakukan operasi yang menghapus semua rekaman dalam database dan dapat berjalan sebelum Post_DeleteMessageHandler_ReturnsRedirectToRoot metode, database disembunyikan ulang dalam metode pengujian ini untuk memastikan bahwa rekaman ada untuk dihapus oleh SUT. Memilih tombol messages hapus pertama formulir di SUT disimulasikan dalam permintaan ke SUT:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    using (var scope = _factory.Services.CreateScope())
    {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<ApplicationDbContext>();

        Utilities.ReinitializeDbForTests(db);
    }

    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);
}

Opsi klien

WebApplicationFactoryClientOptions Lihat halaman untuk default dan opsi yang tersedia saat membuat HttpClient instans.

WebApplicationFactoryClientOptions Buat kelas dan teruskan ke CreateClient() metode :

public class IndexPageTests :
    IClassFixture<CustomWebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<Program>
        _factory;

    public IndexPageTests(
        CustomWebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }

CATATAN: Untuk menghindari peringatan pengalihan HTTPS dalam log saat menggunakan Middleware Pengalihan HTTPS, atur BaseAddress = new Uri("https://localhost")

Menyuntikkan layanan tiruan

Layanan dapat ditimpa dalam pengujian dengan panggilan ke ConfigureTestServices pada pembuat host. Untuk mencakup layanan yang ditimpa untuk pengujian itu WithWebHostBuilder sendiri, metode ini digunakan untuk mengambil pembuat host. Ini dapat dilihat dalam pengujian berikut:

Sampel SUT menyertakan layanan terlingkup yang mengembalikan kuotasi. Kuotasi disematkan di bidang tersembunyi pada halaman Indeks saat halaman Indeks diminta.

Services/IQuoteService.cs:

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

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.");
    }
}

Program.cs:

services.AddScoped<IQuoteService, QuoteService>();

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:

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

Markup berikut dihasilkan saat aplikasi SUT dijalankan:

<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.">

Untuk menguji layanan dan mengutip injeksi dalam pengujian integrasi, layanan tiruan disuntikkan ke SUT oleh pengujian. Layanan tiruan mengganti aplikasi QuoteService dengan layanan yang disediakan oleh aplikasi pengujian, yang disebut TestQuoteService:

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(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices dipanggil, dan layanan terlingkup terdaftar:

[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);
}

Markup yang dihasilkan selama eksekusi pengujian mencerminkan teks kutipan yang disediakan oleh TestQuoteService, sehingga pernyataan lolos:

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

Autentikasi tiruan

Pengujian di AuthTests kelas memeriksa apakah titik akhir yang aman:

  • Mengalihkan pengguna yang tidak diautentikasi ke halaman masuk aplikasi.
  • Mengembalikan konten untuk pengguna yang diautentikasi.

Di SUT, /SecurePage halaman menggunakan AuthorizePage konvensi untuk menerapkan AuthorizeFilter ke halaman. Untuk informasi selengkapnya, lihat Razor Konvensi otorisasi halaman.

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

Get_SecurePageRedirectsAnUnauthenticatedUser Dalam pengujian, diatur WebApplicationFactoryClientOptions untuk melarang pengalihan dengan mengatur AllowAutoRedirect ke 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);
}

Dengan melarang klien untuk mengikuti pengalihan, pemeriksaan berikut dapat dilakukan:

  • Kode status yang dikembalikan oleh SUT dapat diperiksa terhadap hasil yang diharapkan HttpStatusCode.Redirect , bukan kode status akhir setelah pengalihan ke halaman masuk, yang akan menjadi HttpStatusCode.OK.
  • Nilai Location header di header respons dicentang untuk mengonfirmasi bahwa header dimulai dengan http://localhost/Identity/Account/Login, bukan respons halaman masuk akhir, di mana Location header tidak akan ada.

Aplikasi pengujian dapat meniru AuthenticationHandler<TOptions>ConfigureTestServices untuk menguji aspek autentikasi dan otorisasi. Skenario minimal mengembalikan 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, "TestScheme");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

dipanggil TestAuthHandler untuk mengautentikasi pengguna ketika skema autentikasi diatur ke TestScheme tempat AddAuthentication terdaftar untuk ConfigureTestServices. Penting bagi TestScheme skema untuk mencocokkan skema yang diharapkan aplikasi Anda. Jika tidak, autentikasi tidak akan berfungsi.

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

    client.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue(scheme: "TestScheme");

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

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

Untuk informasi selengkapnya tentang WebApplicationFactoryClientOptions, lihat bagian Opsi klien.

Pengujian dasar untuk middleware autentikasi

Lihat repositori GitHub ini untuk pengujian dasar middleware autentikasi. Ini berisi server pengujian yang khusus untuk skenario pengujian.

Mengatur lingkungan

Atur lingkungan di pabrik aplikasi kustom:

public class CustomWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram> where TProgram : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var dbContextDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbContextOptions<ApplicationDbContext>));

            services.Remove(dbContextDescriptor);

            var dbConnectionDescriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbConnection));

            services.Remove(dbConnectionDescriptor);

            // Create open SqliteConnection so EF won't automatically close it.
            services.AddSingleton<DbConnection>(container =>
            {
                var connection = new SqliteConnection("DataSource=:memory:");
                connection.Open();

                return connection;
            });

            services.AddDbContext<ApplicationDbContext>((container, options) =>
            {
                var connection = container.GetRequiredService<DbConnection>();
                options.UseSqlite(connection);
            });
        });

        builder.UseEnvironment("Development");
    }
}

Cara infrastruktur pengujian menyimpulkan jalur akar konten aplikasi

WebApplicationFactory Konstruktor menyimpulkan jalur akar konten aplikasi dengan mencari pada rakitan yang WebApplicationFactoryContentRootAttribute berisi pengujian integrasi dengan kunci yang sama dengan TEntryPoint rakitan System.Reflection.Assembly.FullName. Jika atribut dengan kunci yang benar tidak ditemukan, WebApplicationFactory kembali mencari file solusi (.sln) dan menambahkan nama perakitan TEntryPoint ke direktori solusi. Direktori akar aplikasi (jalur akar konten) digunakan untuk menemukan tampilan dan file konten.

Menonaktifkan penyalinan bayangan

Penyalinan bayangan menyebabkan pengujian dijalankan di direktori yang berbeda dari direktori output. Jika pengujian Anda mengandalkan pemuatan file yang relatif terhadap Assembly.Location dan Anda mengalami masalah, Anda mungkin harus menonaktifkan penyalinan bayangan.

Untuk menonaktifkan penyalinan bayangan saat menggunakan xUnit, buat xunit.runner.json file di direktori proyek pengujian Anda, dengan pengaturan konfigurasi yang benar:

{
  "shadowCopy": false
}

Pembuangan objek

Setelah pengujian IClassFixture implementasi dijalankan, TestServer dan HttpClient dibuang ketika xUnit membuang WebApplicationFactory. Jika objek yang dibuat oleh pengembang memerlukan pembuangan, buang objek tersebut IClassFixture dalam implementasi. Untuk informasi selengkapnya, lihat Menerapkan metode Buang.

Sampel pengujian integrasi

Aplikasi sampel terdiri dari dua aplikasi:

App Direktori proyek Deskripsi
Aplikasi pesan (SUT) src/RazorPagesProject Memungkinkan pengguna untuk menambahkan, menghapus satu, menghapus semua, dan menganalisis pesan.
Menguji aplikasi tests/RazorPagesProject.Tests Digunakan untuk menguji integrasi SUT.

Pengujian dapat dijalankan menggunakan fitur pengujian bawaan IDE, seperti Visual Studio. Jika menggunakan Visual Studio Code atau baris perintah, jalankan perintah berikut pada prompt perintah di tests/RazorPagesProject.Tests direktori:

dotnet test

Organisasi aplikasi pesan (SUT)

SUT adalah Razor sistem pesan Pages dengan karakteristik berikut:

  • Halaman Indeks aplikasi (Pages/Index.cshtml dan Pages/Index.cshtml.cs) menyediakan metode UI dan model halaman untuk mengontrol penambahan, penghapusan, dan analisis pesan (kata rata-rata per pesan).
  • Pesan dijelaskan oleh Message kelas (Data/Message.cs) dengan dua properti: Id (kunci) dan Text (pesan). Properti Text diperlukan dan dibatasi hingga 200 karakter.
  • Pesan disimpan menggunakan database dalam memori Entity Framework†.
  • Aplikasi ini berisi lapisan akses data (DAL) di kelas konteks databasenya, AppDbContext (Data/AppDbContext.cs).
  • Jika database kosong pada startup aplikasi, penyimpanan pesan diinisialisasi dengan tiga pesan.
  • Aplikasi ini menyertakan /SecurePage yang hanya dapat diakses oleh pengguna yang diautentikasi.

† Artikel EF, Uji dengan InMemory, menjelaskan cara menggunakan database dalam memori untuk pengujian dengan MSTest. Topik ini menggunakan kerangka kerja pengujian xUnit . Konsep pengujian dan implementasi pengujian di berbagai kerangka kerja pengujian serupa tetapi tidak identik.

Meskipun aplikasi tidak menggunakan pola repositori dan bukan contoh pola Unit kerja (UoW) yang efektif, Razor Pages mendukung pola pengembangan ini. Untuk informasi selengkapnya, lihat Merancang lapisan persistensi infrastruktur dan Logika pengontrol Pengujian (sampel mengimplementasikan pola repositori).

Menguji organisasi aplikasi

Aplikasi pengujian adalah aplikasi konsol di tests/RazorPagesProject.Tests dalam direktori.

Menguji direktori aplikasi Deskripsi
AuthTests Berisi metode pengujian untuk:
  • Mengakses halaman aman oleh pengguna yang tidak diautentikasi.
  • Mengakses halaman aman oleh pengguna terautentikasi dengan tiruan AuthenticationHandler<TOptions>.
  • Mendapatkan profil pengguna GitHub dan memeriksa login pengguna profil.
BasicTests Berisi metode pengujian untuk perutean dan jenis konten.
IntegrationTests Berisi pengujian integrasi untuk halaman Indeks menggunakan kelas kustom WebApplicationFactory .
Helpers/Utilities
  • Utilities.cs berisi metode yang InitializeDbForTests digunakan untuk menyemai database dengan data pengujian.
  • HtmlHelpers.cs menyediakan metode untuk mengembalikan AngleSharp IHtmlDocument untuk digunakan oleh metode pengujian.
  • HttpClientExtensions.cs menyediakan kelebihan beban untuk SendAsync mengirimkan permintaan ke SUT.

Kerangka kerja pengujian adalah xUnit. Pengujian integrasi dilakukan menggunakan Microsoft.AspNetCore.TestHost, yang mencakup TestServer. Microsoft.AspNetCore.Mvc.Testing Karena paket digunakan untuk mengonfigurasi host pengujian dan server pengujian, TestHost paket dan TestServer tidak memerlukan referensi paket langsung dalam file proyek aplikasi pengujian atau konfigurasi pengembang di aplikasi pengujian.

Pengujian integrasi biasanya memerlukan himpunan data kecil dalam database sebelum eksekusi pengujian. Misalnya, penghapusan panggilan pengujian untuk penghapusan rekaman database, sehingga database harus memiliki setidaknya satu catatan agar permintaan penghapusan berhasil.

Aplikasi sampel menyemai database dengan tiga pesan dalam Utilities.cs pengujian tersebut dapat digunakan saat dijalankan:

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." }
    };
}

Konteks database SUT terdaftar di Program.cs. Panggilan balik aplikasi builder.ConfigureServices pengujian dijalankan setelah kode aplikasi Program.cs dijalankan. Untuk menggunakan database yang berbeda untuk pengujian, konteks database aplikasi harus diganti di builder.ConfigureServices. Untuk informasi selengkapnya, lihat bagian Kustomisasi WebApplicationFactory .

Sumber Daya Tambahan: