简介

在C#开发中引进Blazor,使开发人员有能力将其开发扩展到浏览器中,而无需依靠React、Vue.js和Angular等传统JavaScript结构。

虽然在传统的JavaScript结构中设置测验比较简单,但Blazor需要将一些工具和包整合在一起,然后了解怎么以及在应用程序中测验什么。

这篇文章将介绍怎么为一个简单的Blazor计数器应用程序设置测验,并将其扩展到包括C#开发人员或许想在Blazor应用程序中测验的简直一切内容。

建立一个测验环境

首先,咱们来设置演示项目。

创立一个新项目

在Visual Studio中,点击 “新建“。

在Blazor中测试。一个完整的教程

Web和控制台菜单中,挑选应用程序,然后挑选Blazor服务器应用程序

在Blazor中测试。一个完整的教程

鄙人一页中,继续不进行验证,然后设置项目称号解决方案称号。单击 “创立“。

设置一个测验项目

要设置一个测验项目,从文件菜单的下拉菜单中点击新建解决方案;应该会弹出一个模板窗口。

从左侧边栏的Web 控制台组,挑选测验,挑选xUnit测验项目,然后点击下一步

在Blazor中测试。一个完整的教程

运用与主项目相同的结构版本,然后点击下一步

在Blazor中测试。一个完整的教程

最终,为解决方案和项目设置一个称号,然后点击创立

一旦完成,你的Visual Studio应该有如下的侧边栏。

在Blazor中测试。一个完整的教程

将主项目链接到测验项目

为了使测验项目能够引证和运用主项目,咱们必须在测验项目中创立一个链接,这样咱们就能够从主项目中导入和运用组件、类和接口。

在Visual Studio里边,从左面的侧边栏中右击测验解决方案,挑选编辑项目文件,然后在同一组里边增加<ProjectReference Include="../path/to/main-project/main-project.csproj" /> ,并增加SDK版本。

在Blazor中测试。一个完整的教程

设置测验依靠性

装置bUnit

项目菜单中,点击办理NuGet包,查找bUnit,挑选bUnitbUnit.core,点击增加包,挑选两个解决方案,然后点击OK

在Blazor中测试。一个完整的教程

装置xUnit

这个测验项目被引导为xUnit项目。默许情况下,它自带xUnit包。

装置Moq

Moq是一个断语库,对于测验预期成果是否与回来的成果相匹配很有用。

咱们能够用装置bUnit的相同办法来装置Moq。只需查找并挑选Moq,点击增加包,挑选测验项目,然后点击OK

用bUnit测验

xUnit是一个测验结构,它供给了一个接口,能够在浏览器之外运转Blazor应用程序,并依然经过代码与输出进行交互。

bUnit是一个接口,咱们能够经过它与Blazor组件互动。bUnit供给的接口使咱们有或许在Blazor组件上触发事情,找到组件上的一些元素,并作出断语。

测验设置

要用bUnit测验Blazor应用程序,测验套件必须在测验项目中的一个类中有一个测验用例功用。

测验用例中的代码应该有以下内容。

  • Arrange, 设置一个TestContext (一个用于烘托Blazor组件的虚拟环境)。
  • Act ,将一个组件烘托到测验环境中,触发动作,并提出网络恳求。
  • Assert ,检查事情是否被触发以及是否显示了正确的文本。

作为一个比如,下面的设置说明了上述过程。

using BlazorApp.Pages;
using Bunit;
using Xunit;
namespace BlazorAppTests
{
    public class CounterTest
    {
        [Fact]
        public void RendersSuccessfully()
        {
            using var ctx = new TestContext();
            // Render Counter component.
            var component = ctx.RenderComponent<Counter>();
            // Assert: first, find the parent_name vital element, then verify its content.
            Assert.Equal("Click me", component.Find($".btn").TextContent);
        }
    }
}

从右边的侧边栏,点击测验,然后点击全部运转来运转这个测验。

在Blazor中测试。一个完整的教程

传递参数给组件

有时,组件需要参数才干正确出现。bUnit 供给了一个接口来处理这个问题。

首先,让咱们修改应用程序解决方案中的counter 组件,使其看起来像下面这样。

@page "/counter/{DefaultCount:int?}"
<h1>Counter</h1>
<p>Current count: <span id="counterVal">@currentCount</span></p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
    private int currentCount = 0;
    [Parameter]
    public int DefaultCount { get; set; }
    protected override void OnParametersSet()
    {
        if (DefaultCount != 0)
        {
            currentCount = DefaultCount;
        }
    }
    private void IncrementCount()
    {
        currentCount++;
    }
}

首先,注意到咱们怎么更新了路径,以承受一个DefaultCount 的参数,即一个整数。? 告知Blazor,这个参数是可选的,对组件的运转不是必需的。

接下来,注意到C#代码中的DefaultCount 特点有一个[Parameter] 特点。咱们现已将OnParametersSet 生命周期办法挂起,以便在参数被设置时告诉组件。这保证咱们用它来更新组件currentValue 特点,而不是让组件从零开始计数。

咱们能够在bUnit测验用例顶用以下办法烘托这个组件。

using BlazorApp.Pages;
using Bunit;
using Xunit;
namespace BlazorAppTests
{
    public class CounterTest
    {
        public void RendersSuccessfully()
        {
            using var ctx = new TestContext();
            Action onBtnClickHandler = () => { };
            // Render Counter component.
            var component = ctx.RenderComponent<Counter>(
              parameters =>
                parameters
                      // Add parameters
                  .Add(c => c.DefaultCount, 10)
                  .Add(c => c.OnBtnClick, onBtnClickHandler)
            );
            // Assert: first find the parent_name strong element, then verify its content.
            Assert.Equal("Click me", component.Find($".btn").TextContent);
        }
    }
}

在上面的测验中的第14行,咱们烘托组件,然后传递一个回调给组件,调用(p => );

然后,咱们将Add 办法增加到参数(p => p.Add(c => c.DefaultCount, 10); ,以便将该参数设置为10。

咱们能够用相同的办法传递一个事情回调,即p.Add(c => c.onBtnClickHandler, onBtnClickHandler) 。这样,咱们在onBtnClickHandler 动作中完成了计数器的递增,而不是在counter 组件中。

将输入和服务传递给组件

有些组件依靠外部服务来运转,而有些则依靠外部字段。咱们能够经过测验上下文中的Services.AddSingleton 办法,用bUnit来完成这一点。

在演示的计数器应用里边,有一个FetchData.razor 文件,它严重依靠一个WeatherForecastService 服务。让咱们测验在xUnit测验项目中运转这个文件。

在测验项目中创立一个名为FetchDataTest.cs 的新文件,并增加以下内容。

using System;
using BlazorApp.Data;
using BlazorApp.Pages;
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace BlazorAppTests
{
    public class FetchDataTest
    {
        [Fact]
        public void RendersSuccessfully()
        {
            using var ctx = new TestContext();
            ctx.Services.AddSingleton<WeatherForecastService>(new WeatherForecastService());
            // Render Counter component.
            var component = ctx.RenderComponent<FetchData>();
            Assert.Equal("Weather forecast", component.Find($"h1").TextContent);
        }
    }
}

注意咱们是怎么运用AddSingleton 接口来增加一个新的服务到咱们的测验运转器上下文的。而当咱们运转这个测验文件时,咱们应该得到一个成功的成果。

事情

上面,咱们看到了如安在测验用例组件内为一个事情设置回调。让咱们看看如安在组件内的一个元素上触发事情。

计数器测验文件有一个按钮,当点击时,会增加计数器。让咱们测验一下,保证咱们能够点击这个按钮,看到页面上的计数更新。

在测验项目中的CounterTest.cs 文件内,在CounterTest 测验套件类中增加以下测验事例。

[Fact]
public void ButtonClickAndUpdatesCount()
{
    // Arrange
    using var ctx = new TestContext();
    var component = ctx.RenderComponent<Counter>();
    // Render
    var counterValue = "0";
    Assert.Equal(counterValue, component.Find($"#counterVal").TextContent);
    counterValue = "1";
    var buttonElement = component.Find("button");
    buttonElement.Click();
    Assert.Equal(counterValue, component.Find($"#counterVal").TextContent);
}

在 “摆放 “部分设置了该组件。像往常相同,在 “Render “部分,咱们首先断语该组件从零开始。

然后,咱们运用测验上下文组件的.Find 接口取得按钮的引证,这时回来元素的引证,它也有一些像Click() 办法的API。

最终,咱们断语组件的值,以承认按钮的点击会做相同的动作。

等候异步的状态更新

请注意,在注入服务后,咱们没有测验是否有任何数据被烘托。就像FetchData.razor 组件相同,有些组件需要时刻才干烘托出正确的数据。

咱们能够经过component.waitForState(fn, duration) 办法来等候异步状态的更新。

[Fact]
public void RendersServiceDataSuccessfully()
{
    using var ctx = new TestContext();
    ctx.Services.AddSingleton<WeatherForecastService>(new WeatherForecastService());
    // Render Counter component.
    var component = ctx.RenderComponent<FetchData>();
    component.WaitForState(() => component.Find(".date").TextContent == "Date");
    Assert.Equal("TABLE", component.Find($".table").NodeName);
}

上面的比如等候异步数据的加载,直到WaitForState 中的匿名函数被调用,该函数测验找到一个具有date 类的元素。一旦找到了,咱们就能够对成果做一些进一步的断语。

验证符号

咱们还能够经过MarkupMatches bUnit接口办法验证一个组件的符号是否遵从相同的形式。

例如,咱们能够测验索引是否包含有 “Hello, World!”文本内容的h1

首先,在测验项目内创立一个新文件,命名为IndexTest.cs ,并增加以下内容。

using System;
using BlazorApp.Pages;
using Bunit;
using Xunit;
namespace BlazorAppTests
{
    public class IndexTest
    {
        [Fact]
        public void RendersSuccessfully()
        {
            using var ctx = new TestContext();
            // Act
            var component = ctx.RenderComponent<BlazorApp.Pages.Index>();
            // Assert
            Assert.Equal("Hello, world!", component.Find($"h1").TextContent);
        }
    }
}

除此以外,咱们还能够经过.Find (咱们现已在这样做了),和FindAll ,来验证一个组件是否包含一个元素,它能够回来一切与查询相匹配的特征。这些办法采用了相似于CSS的挑选器,这使咱们更简单遍历节点。

嘲弄IJSRuntime

IJSRuntime是一个接口,它使得从.Net代码中与JavaScript交互成为或许。

一些组件或许依靠于它;例如,一个组件能够运用jQuery办法来进行API调用。

假如咱们的项目中有JavaScript函数getPageTitle ,咱们能够模仿该函数的调用,这样在咱们组件的任何地方,其成果将是咱们在测验事例中或许指定的。

using var ctx = new TestContext();
ctx.Services.AddSingleton<WeatherForecastService>(new WeatherForecastService());
var theResult = "some result";
ctx.JSInterop.Setup<string>("getPageTitme").SetResult(theResult);
// Render Counter component.
var component = ctx.RenderComponent<FetchData>();
Assert.Equal(theResult, component.Find($".page-title").TextContent);

嘲弄HttpClient

一些应用程序依靠来自长途服务器的数据来正常运转。

单元测验的部分战略是使每个测验用例的依靠性不受影响。而依靠HTTP客户端接触到长途服务器的组件来出现一个功用,假如成果不是静态的,就会破坏咱们的测验。

咱们能够经过模仿HTTPClient来消除这个问题,HTTPClient是一个能够从Blazor应用内部向外部国际发出HTTP恳求的库。

根据bUnit的文档,bUnit默许不包含这个功用,但咱们能够依靠第三方库来完成这个功用。

首先,将RichardSzalay.MockHttp包增加到测验项目中。

dotnet add package RichardSzalay.MockHttp --version 6.0.0

接下来,在测验项目的根部创立一个名为MockHttpClientBunitHelpers 的文件,并增加以下内容。

using Bunit;
using Microsoft.Extensions.DependencyInjection;
using RichardSzalay.MockHttp;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
public static class MockHttpClientBunitHelpers
{
    public static MockHttpMessageHandler AddMockHttpClient(this TestServiceProvider services)
    {
        var mockHttpHandler = new MockHttpMessageHandler();
        var httpClient = mockHttpHandler.ToHttpClient();
        httpClient.BaseAddress = new Uri("http://localhost");
        services.AddSingleton<HttpClient>(httpClient);
        return mockHttpHandler;
    }
    public static MockedRequest RespondJson<T>(this MockedRequest request, T content)
    {
        request.Respond(req =>
        {
            var response = new HttpResponseMessage(HttpStatusCode.OK);
            response.Content = new StringContent(JsonSerializer.Serialize(content));
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            return response;
        });
        return request;
    }
    public static MockedRequest RespondJson<T>(this MockedRequest request, Func<T> contentProvider)
    {
        request.Respond(req =>
        {
            var response = new HttpResponseMessage(HttpStatusCode.OK);
            response.Content = new StringContent(JsonSerializer.Serialize(contentProvider()));
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            return response;
        });
        return request;
    }
}

现在,创立一个新的测验事例,并增加以下内容。

[Fact]
public void FetchResultTest()
{
    var serverTime = "1632114204";
    using var ctx = new TestContext();
    var mock = ctx.Services.AddMockHttpClient();
    mock.When("/getTime").RespondJson<string>(serverTime);
    // Render Counter component.
    var component = ctx.RenderComponent<FetchData>();
    Assert.Equal(serverTime, component.Find($".time").TextContent);
}

在这里,咱们声明了一个变量,用来保存咱们对服务器的希望,然后经过一个bUnit辅助办法ctx.Services.AddMockHttpClient ,将模仿的客户端增加到上下文服务中,该办法将寻觅MockHttpClientBunitHelpers ,并将其注入到上下文。

然后,咱们运用模仿的引证来模仿咱们希望从路由中得到的响应。最终,咱们断语咱们组件的一部分具有咱们从模仿恳求回来的值。

总结

在这篇文章中,咱们看到了怎么设置一个Blazor项目并增加另一个xUnit测验项目。咱们还将bUnit作为一个测验结构,并讨论了运用bUnit来测验Blazor组件。

除了xUnit作为一个测验结构外,bUnit还能够在nUnit测验结构中运用相似的概念和API运转。

在这篇文章中,咱们介绍了bUnit的一般用法。高级用法可在bUnit文档网站上找到。

The postTesting in Blazor:完好的教程》首先出现在LogRocket博客上。