ASP.NET Core 中的整合測試

Javier Calvarro NelsonSteve SmithJos van der 磚

整合測試可確保應用程式的元件在包含應用程式支援基礎結構的層級(例如資料庫、檔案系統和網路)正常運作。 ASP.NET Core 使用單元測試架構搭配測試 web 主機和記憶體中的測試伺服器來支援整合測試。

本主題假設您對單元測試有基本的瞭解。 如果不熟悉測試概念,請參閱 .Net Core 中的單元測試和 .NET Standard 主題及其連結的內容。

查看或下載範例程式碼 (如何下載)

範例應用程式是 Razor 頁面應用程式,並假設對頁面有基本的瞭解 Razor 。 如果不熟悉 Razor 頁面,請參閱下列主題:

注意

針對測試 Spa,我們建議使用 Playwright for .net之類的工具,它可以將瀏覽器自動化。

整合測試簡介

整合測試會評估應用程式元件在更廣泛的層級上,而不是 單元測試。 單元測試是用來測試隔離的軟體元件,例如個別的類別方法。 整合測試會確認兩個或多個應用程式元件會一起運作,以產生預期的結果,可能包括完整處理要求所需的每個元件。

這些廣泛的測試是用來測試應用程式的基礎結構和整個架構,通常包括下列元件:

  • 資料庫
  • 檔案系統
  • 網路設備
  • 要求-回應管線

單元測試使用製造的元件(稱為 fakesmock 物件)來取代基礎結構元件。

相對於單元測試,整合測試:

  • 使用應用程式在生產環境中使用的實際元件。
  • 需要更多程式碼和資料處理。
  • 執行需要較長的時間。

因此,請將整合測試的使用限制在最重要的基礎結構案例中。 如果您可以使用單元測試或整合測試來測試行為,請選擇單元測試。

在整合測試的討論中,測試過的專案通常稱為 受測試的系統,或簡稱為「SUT」。 本主題中使用 "SUT" 來參考經過測試的 ASP.NET Core 應用程式。

提示

請勿針對每個可能的資料和檔案存取,使用資料庫和檔案系統來撰寫整合測試。 無論應用程式之間有多少位置與資料庫和檔案系統互動,一組專注的讀取、寫入、更新和刪除整合測試,通常都能充分測試資料庫和檔案系統元件。 針對與這些元件互動的方法邏輯,使用單元測試進行常式測試。 在單元測試中,使用基礎結構 fakes/模擬會導致更快速的測試執行。

ASP.NET Core 整合測試

ASP.NET Core 中的整合測試需要下列各項:

  • 測試專案可用來包含和執行測試。 測試專案具有此 SUT 的參考。
  • 測試專案會建立適用于該 SUT 的測試 web 主機,並使用測試伺服器用戶端來處理與該 SUT 的要求和回應。
  • 測試執行器會用來執行測試並報告測試結果。

整合測試會遵循一連串的事件,其中包含一般的 排列ActAssert 測試步驟:

  1. 已設定 SUT 的 web 主機。
  2. 建立測試伺服器用戶端以將要求提交給應用程式。
  3. 執行「 排列 測試」步驟:測試應用程式準備要求。
  4. 執行 Act 測試步驟:用戶端會提交要求並接收回應。
  5. 執行 Assert 測試步驟:實際 的回應會根據 預期 的回應,驗證為 通過失敗
  6. 此程式會繼續執行,直到執行所有測試為止。
  7. 系統會報告測試結果。

測試 web 主機的設定方式通常與測試回合的應用程式一般 web 主機不同。 例如,可能會使用不同的資料庫或不同的應用程式設定來進行測試。

基礎結構元件,例如測試 web 主機和記憶體中的測試伺服器 (TestServer) ,是由 AspNetCore 套件提供或管理。 使用這個封裝可簡化測試的建立和執行。

Microsoft.AspNetCore.Mvc.Testing封裝會處理下列工作:

  • 將相依性檔案 (.deps) 從 SUT 複製到測試專案的 bin 目錄。
  • 內容根目錄 設定為 SUT 的專案根目錄,以便在執行測試時找到靜態檔案和頁面/瀏覽器。
  • 提供 WebApplicationFactory 類別,以簡化使用來啟動載入的工作 TestServer

單元測試檔會描述如何設定測試專案和測試執行器,以及如何執行測試和建議的詳細指示,以瞭解如何命名測試和測試類別。

注意

建立應用程式的測試專案時,請將整合測試中的單元測試分成不同的專案。 這有助於確保基礎結構測試元件不會不慎包含在單元測試中。 單元和整合測試的分隔也可讓您控制要執行的測試集。

Razor頁面應用程式和 MVC 應用程式的測試設定幾乎沒有任何差異。 唯一的差異在於測試的命名方式。 在 Razor 頁面應用程式中,頁面端點的測試通常會在頁面模型類別之後命名 (例如,用 IndexPageTests 來測試索引頁面) 的元件整合。 在 MVC 應用程式中,測試通常會依控制器類別來組織,並在測試的控制器之後命名 (例如, HomeControllerTests 以測試控制器) 的元件整合 Home 。

測試應用程式必要條件

測試專案必須:

您可以在 範例應用程式中看到這些必要條件。 檢查檔案 tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj 。 範例應用程式會使用 xUnit 測試架構和 AngleSharp 剖析器程式庫,因此範例應用程式也會參考:

在使用 xunit.runner.visualstudio 版本2.4.2 或更新版本的應用程式中,測試專案必須參考 Microsoft.NET.Test.Sdk 封裝。

測試中也會使用 Entity Framework Core。 應用程式參考:

SUT 環境

如果未設定 SUT 的 環境 ,則環境會預設為開發。

使用預設 WebApplicationFactory 的基本測試

WebApplicationFactory<TEntryPoint> 用來建立 TestServer 整合測試的。 TEntryPoint 是此 SUT 的進入點類別,通常是 Startup 類別。

測試類別會執行 類別裝置 介面 (IClassFixture) 指出類別包含測試,並在類別中的測試之間提供共用物件實例。

下列測試類別會 BasicTests 使用 WebApplicationFactory 來啟動載入並提供 HttpClient 測試方法的 Get_EndpointsReturnSuccessAndCorrectContentType 。 方法會檢查回應狀態碼是否成功 (狀態碼在 200-299) 範圍內,且 Content-Type 標頭 text/html; charset=utf-8 適用于數個應用程式頁面。

CreateClient 建立的實例 HttpClient ,這個實例會自動遵循重新導向和處理 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());
    }
}

依預設,在 cookie 啟用 GDPR 同意原則 時,不會在要求之間保留非必要的。 若要保留非必要的 cookie ,例如 TempData 提供者所使用的,請在您的測試中將它們標示為必要。 如需將 a 標示 cookie 為必要的指示,請參閱 重要的 cookie s

自訂 WebApplicationFactory

您可以藉由繼承 WebApplicationFactory 來建立一或多個自訂處理站,以獨立于測試類別建立 Web 主機設定:

  1. 繼承自 WebApplicationFactory 和覆寫 ConfigureWebHostIWebHostBuilder允許將服務集合設定為 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);
                    }
                }
            });
        }
    }
    

    範例應用程式中的資料庫植入是由 InitializeDbForTests 方法執行。 整合測試範例:測試應用程式組織一節中會說明方法。

    已在其方法中註冊的 SUT 資料庫內容 Startup.ConfigureServices 。 測試應用程式的 builder.ConfigureServices 回呼會在應用程式的程式 Startup.ConfigureServices 代碼執行之後執行。 執行順序是 ASP.NET Core 3.0 版本之泛型主機的重大變更。 若要針對測試使用與應用程式資料庫不同的資料庫,則必須在中取代應用程式的資料庫內容 builder.ConfigureServices

    對於仍在使用 Web 主機的 SUTs,測試應用程式的 builder.ConfigureServices 回呼會在 SUT 的程式碼 之前 執行 Startup.ConfigureServices 。 測試應用程式的 builder.ConfigureTestServices 回呼會 在之後 執行。

    範例應用程式會尋找資料庫內容的服務描述項,並使用描述項來移除服務註冊。 接下來,factory 會加入新 ApplicationDbContext 的,其會使用記憶體內部資料庫進行測試。

    若要連接到與記憶體內部資料庫不同的資料庫,請變更將 UseInMemoryDatabase 內容連接到不同資料庫的呼叫。 若要使用 SQL Server 測試資料庫:

    services.AddDbContext<ApplicationDbContext>((options, context) => 
    {
        context.UseSqlServer(
            Configuration.GetConnectionString("TestingDbConnectionString"));
    });
    
  2. CustomWebApplicationFactory在測試類別中使用自訂。 下列範例會使用類別中的 factory IndexPageTests

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

    範例應用程式的用戶端設定為防止 HttpClient 下列重新導向。 如稍後在 Mock 驗證 區段中所述,這會允許測試檢查應用程式第一個回應的結果。 第一個回應是其中許多測試中有標頭的重新導向 Location

  3. 一般的測試會使用 HttpClient 和 helper 方法來處理要求和回應:

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

對所有的 SUT 進行 POST 要求都必須滿足應用程式 資料保護 antiforgery 系統自動進行的 antiforgery 檢查。 為了排列測試的 POST 要求,測試應用程式必須:

  1. 提出頁面要求。
  2. cookie從回應中剖析 antiforgery 和要求驗證 token。
  3. 使用 antiforgery cookie 和要求驗證權杖來發出 POST 要求。

SendAsyncHelper 擴充方法 (Helpers/HttpClientExtensions.cs) ,而 GetDocumentAsync Helpers/HtmlHelpers.cs 範例應用程式 () 中的 helper 方法會使用AngleSharp剖析器,利用下列方法來處理 antiforgery 檢查:

  • GetDocumentAsync:接收 HttpResponseMessage 並傳回 IHtmlDocumentGetDocumentAsync 使用可根據原始的來準備 虛擬回應 的 factory HttpResponseMessage 。 如需詳細資訊,請參閱 AngleSharp 檔
  • SendAsync``HttpClient撰寫 HttpRequestMessage 和呼叫 SendAsync(HttpRequestMessage) 的擴充方法,以將要求提交至 SUT。 SendAsync接受 HTML 表單 (IHtmlFormElement) 的多載,以及下列各項:
    • 表單 () 的 [提交] 按鈕 IHtmlElement
    • 表單值集合 (IEnumerable<KeyValuePair<string, string>>)
    • 提交按鈕 (IHtmlElement) 和表單值 (IEnumerable<KeyValuePair<string, string>>)

注意

AngleSharp 是協力廠商剖析程式庫,用於本主題和範例應用程式中的示範用途。 ASP.NET Core 應用程式的整合測試不支援或不需要 AngleSharp。 您可以使用其他剖析器,例如 (HAP) 的 Html 敏捷套件 。 另一種方法是撰寫程式碼來處理 antiforgery 系統的要求驗證權杖,並 cookie 直接 antiforgery。

使用 WithWebHostBuilder 自訂用戶端

在測試方法中需要進行其他設定時,會 WithWebHostBuilder 使用設定 WebApplicationFactory IWebHostBuilder 進一步自訂的來建立新的。

Post_DeleteMessageHandler_ReturnsRedirectToRoot範例應用程式的測試方法會示範的使用方式 WithWebHostBuilder 。 這項測試會在 SUT 中觸發表單提交,在資料庫中執行記錄刪除。

因為類別中的另一個測試 IndexPageTests 會執行刪除資料庫中所有記錄的作業,而且可能會在方法之前執行,所以會 Post_DeleteMessageHandler_ReturnsRedirectToRoot 在此測試方法中重新植入資料庫,以確保有記錄存在,以便讓 SUT 刪除。 在 sut 的 messages 要求中,會模擬在 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);
}

用戶端選項

下表顯示 WebApplicationFactoryClientOptions 建立實例時可用的預設值 HttpClient

選項 描述 預設
AllowAutoRedirect 取得或設定 HttpClient 實例是否應該自動遵循重新導向回應。 true
BaseAddress 取得或設定實例的基底位址 HttpClient http://localhost
HandleCookies 取得或設定 HttpClient 實例是否應該處理 cookie s。 true
MaxAutomaticRedirections 取得或設定實例應遵循的重新導向回應數目上限 HttpClient 7

建立 WebApplicationFactoryClientOptions 類別,並將它傳遞給 CreateClient 方法 (預設值會顯示在程式碼範例) 中:

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

插入 mock 服務

在主機建立器上呼叫時,可以在測試中覆寫服務 ConfigureTestServices若要插入模擬服務,則必須具有 Startup 具有方法的類別 Startup.ConfigureServices

範例 SUT 包含會傳回報價的範圍服務。 當要求索引頁面時,引號會內嵌在索引頁面的隱藏欄位中。

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

執行 SUT 應用程式時,會產生下列標記:

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

若要在整合測試中測試服務和報價插入,模擬服務會由測試插入至 SUT。 模擬服務會將應用程式取代為 QuoteService 測試應用程式所提供的服務,稱為 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 會呼叫,且已註冊範圍服務:

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

測試執行期間所產生的標記會反映所提供的報價文字 TestQuoteService ,因此判斷提示會通過:

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

Mock 驗證

類別中的測試 AuthTests 會檢查安全的端點:

  • 將未經驗證的使用者重新導向至應用程式的登入頁面。
  • 傳回已驗證使用者的內容。

在 SUT 中, /SecurePage 頁面會使用慣例將套用 AuthorizePage AuthorizeFilter 至頁面。 如需詳細資訊,請參閱 Razor 頁面授權慣例

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

Get_SecurePageRedirectsAnUnauthenticatedUser 測試中,會將設定為 [不允許重新導向],方法是將 WebApplicationFactoryClientOptions 設定 AllowAutoRedirectfalse

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

藉由不允許用戶端遵循重新導向,可進行下列檢查:

  • 在重新導向至登入頁面之後,可針對預期的結果(而不是最後的狀態碼)檢查由您所傳回的狀態碼, HttpStatusCode.Redirect 這會是 HttpStatusCode。確定
  • Location檢查回應標頭中的標頭值,以確認它的開頭 http://localhost/Identity/Account/Login 不是最後一個登入頁面回應,而且 Location 標頭不會出現。

測試應用程式可以模擬中的,以便 AuthenticationHandler<TOptions> ConfigureTestServices 測試驗證和授權的層面。 基本案例會傳回 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);
    }
}

TestAuthHandler當驗證配置設定為的註冊位置時,會呼叫來驗證 Test 使用者 AddAuthentication ConfigureTestServicesTest配置必須符合您的應用程式所預期的配置是很重要的。 否則,驗證將無法運作。

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

如需的詳細資訊 WebApplicationFactoryClientOptions ,請參閱 用戶端選項 一節。

設定環境

根據預設,已將 SUT 的主機和應用程式環境設定為使用開發環境。 若要在使用時覆寫 SUT 的環境 IHostBuilder

  • 設定 ASPNETCORE_ENVIRONMENT 環境變數 (例如,、 Staging Production 或其他自訂值,例如 Testing) 。
  • CreateHostBuilder在測試應用程式中覆寫,以讀取前面加上的環境變數 ASPNETCORE
protected override IHostBuilder CreateHostBuilder() =>
    base.CreateHostBuilder()
        .ConfigureHostConfiguration(
            config => config.AddEnvironmentVariables("ASPNETCORE"));

如果 SUT 使用 () 的 Web 主機 IWebHostBuilder ,請覆寫 CreateWebHostBuilder

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

測試基礎結構如何推斷應用程式內容根路徑

此函式會藉 WebApplicationFactory 由在 WebApplicationFactoryContentRootAttribute 包含整合測試(具有等於元件的金鑰)的元件上搜尋,來推斷應用程式內容的根路徑 TEntryPoint System.Reflection.Assembly.FullName 。 如果找不到具有正確索引鍵的屬性,則會 WebApplicationFactory 切換回以搜尋方案檔 (.Sln) 並將 TEntryPoint 元件名稱附加至方案目錄。 應用程式根目錄 (內容根路徑) 用來探索視圖和內容檔案。

停用陰影複製

陰影複製會導致測試在和輸出目錄不同的目錄中執行。 如果您的測試依賴載入相對於的檔案, Assembly.Location 而且您遇到問題,您可能必須停用陰影複製。

若要在使用 xUnit 時停用陰影複製,請 xunit.runner.json 使用 正確的設定設定在測試專案目錄中建立檔案:

{
  "shadowCopy": false
}

物件的處置

在執行執行的測試之後 IClassFixtureTestServer 以及 HttpClient 在 xUnit 處置時處置 WebApplicationFactory 。 如果開發人員所具現化的物件需要處置,請在執行時處置它們 IClassFixture 。 如需詳細資訊,請參閱 執行 Dispose 方法

整合測試範例

範例應用程式是由兩個應用程式所組成:

應用程式 專案目錄 描述
(在 SUT) 的訊息應用程式 src/RazorPagesProject 允許使用者新增、刪除、刪除所有訊息,以及分析訊息。
測試應用程式 tests/RazorPagesProject.Tests 用於整合測試的 SUT。

您可以使用 IDE 的內建測試功能(例如Visual Studio)來執行測試。 如果使用Visual Studio Code或命令列,請在目錄中的命令提示字元執行下列命令 tests/RazorPagesProject.Tests

dotnet test

(的 SUT) 組織的訊息應用程式

SUT 是 Razor 具有下列特性的頁面訊息系統:

  • 應用程式 (和) 的 [索引] 頁面會 Pages/Index.cshtml Pages/Index.cshtml.cs 提供 UI 和頁面模型方法,以控制訊息的新增、刪除和分析 (每個訊息) 的平均單字。
  • Message Data/Message.cs) 有兩個屬性的類別 (描述訊息: Id (索引鍵) 和 Text (訊息) 。 Text屬性是必要的,且限制為200個字元。
  • 訊息是使用 Entity Framework 的記憶體內部資料庫† 來儲存。
  • 應用程式在其資料庫內容類別中包含 (DAL) 的資料存取層, AppDbContext (Data/AppDbContext.cs) 。
  • 如果應用程式啟動時資料庫是空的,則會使用三個訊息來初始化訊息存放區。
  • 應用程式包含 /SecurePage 只能由已驗證的使用者存取的。

†EF 主題、 使用 InMemory 進行測試,說明如何使用記憶體內部資料庫來搭配 MSTest 進行測試。 本主題使用 xUnit 測試架構。 跨不同測試架構的測試概念和測試的執行方式類似,但不完全相同。

雖然應用程式不會使用存放庫模式,而不是 工作單元) (的有效範例,但 Razor 頁面支援這些開發模式。 如需詳細資訊,請參閱 設計基礎結構持續性層測試控制器邏輯 (範例會) 執行儲存機制模式。

測試應用程式組織

測試應用程式是目錄中的主控台應用程式 tests/RazorPagesProject.Tests

測試應用程式目錄 Description
AuthTests 包含的測試方法:
  • 未經驗證的使用者存取安全的頁面。
  • 使用模擬以已驗證的使用者存取安全的頁面 AuthenticationHandler<TOptions>
  • 取得 GitHub 的使用者設定檔,並檢查設定檔的使用者登入。
BasicTests 包含路由和內容類型的測試方法。
IntegrationTests 包含使用自訂類別之 [索引] 頁面的整合測試 WebApplicationFactory
Helpers/Utilities
  • Utilities.cs 包含 InitializeDbForTests 用來將測試資料植入資料庫的方法。
  • HtmlHelpers.cs 提供方法,以傳回 IHtmlDocument 測試方法所使用的 AngleSharp。
  • HttpClientExtensions.cs 提供的多載, SendAsync 以將要求提交至 SUT。

測試架構為 xUnit。 整合測試是使用執行的 Microsoft.AspNetCore.TestHost ,其中包含 TestServer 。 因為此 Microsoft.AspNetCore.Mvc.Testing 套件是用來設定測試主機和測試伺服器,所以和套件在測試應用程式的 TestHost TestServer 專案檔或開發人員設定中不需要直接封裝參考。

在測試執行之前,整合測試通常需要資料庫中的小型資料集。 例如,刪除測試會呼叫資料庫記錄刪除,因此資料庫必須至少有一筆記錄,刪除要求才能成功。

範例應用程式會在資料庫中植入三則訊息 Utilities.cs ,讓測試在執行時可以使用:

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

已在其方法中註冊的 SUT 資料庫內容 Startup.ConfigureServices 。 測試應用程式的 builder.ConfigureServices 回呼會在應用程式的程式 Startup.ConfigureServices 代碼執行之後執行。 若要使用不同的資料庫進行測試,必須在中取代應用程式的資料庫內容 builder.ConfigureServices 。 如需詳細資訊,請參閱 自訂 WebApplicationFactory 一節。

對於仍在使用 Web 主機的 SUTs,測試應用程式的 builder.ConfigureServices 回呼會在 SUT 的程式碼 之前 執行 Startup.ConfigureServices 。 測試應用程式的 builder.ConfigureTestServices 回呼會 在之後 執行。

其他資源