[英]How to inject NavigationManager in bunit Blazor component unit test
[英]How can I unit test a blazor component with Http and a Timer?
我創建了一個 Blazor 組件,它定期從 api 收集價格:
@page "/"
@using System.Globalization;
@using System.Net.Http;
@using BitcoinChallenge.Entities;
@using Newtonsoft.Json;
@inject BitcoinChallengeSettings BitcoinSettings;
@inject HttpClient Http;
@inject IJSRuntime JSRuntime;
<div class="card" style="width: 20rem;">
<h5 class="card-title">Current price of one bitcoin</h5>
<p>@FormattedPrice</p>
</div>
@code{
private const string BitcoinPriceProvider = "https://api.coinbase.com/v2/prices/spot?currency=EUR";
private static readonly CultureInfo cultureInfo = new CultureInfo("de-DE", false);
protected decimal Price { get; set; }
protected string FormattedPrice => this.Price.ToString("c", cultureInfo);
protected override async Task OnInitializedAsync() {
try {
this.Price = await this.fetchPrice();
this.startPeriodicRefresh();
}
catch (Exception e) {
await JSRuntime.InvokeAsync<object>("alert", e.ToString());
}
}
private void startPeriodicRefresh() {
TimeSpan startTimeSpan = TimeSpan.Zero;
TimeSpan periodTimeSpan = TimeSpan.FromSeconds(BitcoinSettings.RefreshTimeInSeconds);
var timer = new System.Threading.Timer(async (e) => {
this.Price = await this.fetchPrice();
}, null, startTimeSpan, periodTimeSpan);
}
private async Task<decimal> fetchPrice() {
HttpResponseMessage priceResponse = await Http.GetAsync(BitcoinPriceProvider);
priceResponse.EnsureSuccessStatusCode();
string responseBody = await priceResponse.Content.ReadAsStringAsync();
BitcoinPriceWrapper bitcoinPriceWrapper = JsonConvert.DeserializeObject<BitcoinPriceWrapper>(responseBody);
decimal amount = decimal.Parse(bitcoinPriceWrapper.Data.Amount);
return amount;
}
}
我想創建一個測試來證明這是按預期工作的,我已經創建了這個測試:
using BitcoinChallenge.Entities;
using BitcoinChallengeBlazorApp;
using Microsoft.AspNetCore.Components.Testing;
using Microsoft.JSInterop;
using Moq;
using Nancy.Json;
using RichardSzalay.MockHttp;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Xunit;
using Index = BitcoinChallengeBlazorApp.Pages.Index;
namespace BitcoinChalllengeBlazorApp.UnitTests {
public class IndexTest {
readonly TestHost host = new TestHost();
readonly decimal testAmount = 8448.947391885M;
readonly int testRefreshRate = 10;
[Fact]
public void Test1() {
// Arrange
_ = this.SetMockRuntime();
_ = this.CreateMockHttpClientAsync();
_ = this.CreateSettings();
// Act
RenderedComponent<Index> componentUnderTest = this.host.AddComponent<Index>();
// Assert
Assert.Equal($"{this.testAmount:n2}", componentUnderTest.Find("p").InnerText);
}
public Mock<IJSRuntime> SetMockRuntime() {
Mock<IJSRuntime> jsRuntimeMock = new Mock<IJSRuntime>();
this.host.AddService(jsRuntimeMock.Object);
return jsRuntimeMock;
}
public HttpClient CreateMockHttpClientAsync() {
MockHttpMessageHandler mockHttpMessageHandler = new MockHttpMessageHandler();
mockHttpMessageHandler.When("https://api.coinbase.com/v2/prices/spot?currency=EUR")
.Respond(this.CreateMockResponse);
HttpClient httpClient = new HttpClient(mockHttpMessageHandler);
this.host.AddService(httpClient);
return httpClient;
}
private Task<HttpResponseMessage> CreateMockResponse() {
BitcoinPriceWrapper bitcoinPriceWrapper = new BitcoinPriceWrapper();
bitcoinPriceWrapper.Data = new BitcoinPrice();
bitcoinPriceWrapper.Data.Amount = this.testAmount.ToString();
HttpResponseMessage mockResponse = new HttpResponseMessage(HttpStatusCode.OK) {
Content = new StringContent(new JavaScriptSerializer().Serialize(bitcoinPriceWrapper))
};
mockResponse.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return Task.FromResult(mockResponse);
}
private BitcoinChallengeSettings CreateSettings() {
AppSettings appSettings = new AppSettings {
RefreshTimeInSeconds = this.testRefreshRate
};
BitcoinChallengeSettings bitcoinChallengeSettings = new BitcoinChallengeSettings(appSettings);
this.host.AddService(bitcoinChallengeSettings);
return bitcoinChallengeSettings;
}
}
}
但是,此測試失敗並顯示:
BitcoinChallengeBlazorApp.UnitTests.IndexTest.Test1 來源:IndexTest.cs 第 23 行持續時間:51.5 秒
消息:Assert.Equal() 失敗↓ (pos 0) 預期:8,448.95 實際:0,00 € ↑ (pos 0) 堆棧跟蹤:IndexTest.Test1() 第 34 行
通過對 Visual Studio 調試器的試驗,在我看來,正在發生的事情是被模擬的 Http 客戶端和計時器正在“合謀”導致看似總是失敗的不穩定結果。
正如預期的那樣,我可以觀察到表達式decimal.Parse(bitcoinPriceWrapper.Data.Amount)
多次執行,有時會導致值“8448.947391885”,但有時會導致值 null 或 0。
我還沒有觀察到這種模式,也沒有觀察到在測試完成之前實際執行了多少次,但我注意到 Timer 從來沒有真正等待(老實說,我可能不希望它等待)和測試總是失敗。
amount
總是得到一個值?謝謝。
我想出了如何穩定 Http 客戶端模擬。
首先,我創建了一個新的處理程序:
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace BitcoinChalllengeBlazorApp.UnitTests {
public class MockBitcoinMessageHandler : HttpMessageHandler {
public static int requestCount = 0;
private readonly Func<Task<HttpResponseMessage>> createMockResponse;
public MockBitcoinMessageHandler(Func<Task<HttpResponseMessage>> createMockResponse) {
this.createMockResponse = createMockResponse;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken
) {
requestCount++;
return this.createMockResponse();
}
}
}
利用這一點,我修改了 HttpClient 的創建:
private MockBitcoinMessageHandler CreateMockHttpClientAsync() {
MockBitcoinMessageHandler mockHttpMessageHandler = new MockBitcoinMessageHandler(this.CreateMockResponse);
HttpClient httpClient = new HttpClient(mockHttpMessageHandler);
this.host.AddService(httpClient);
return mockHttpMessageHandler;
}
private Task<HttpResponseMessage> CreateMockResponse() {
BitcoinPriceWrapper bitcoinPriceWrapper = new BitcoinPriceWrapper {
Data = new BitcoinPrice {
Amount = this.testAmount.ToString()
}
};
HttpResponseMessage mockResponse = new HttpResponseMessage(HttpStatusCode.OK) {
Content = new StringContent(new JavaScriptSerializer().Serialize(bitcoinPriceWrapper))
};
mockResponse.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return Task.FromResult(mockResponse);
}
最后,我修改了我的斷言:
string displayAmount = componentUnderTest.Find("p").InnerText.Split(new char[] { ' ' })[0];
NumberStyles style = NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands;
CultureInfo provider = new CultureInfo("de-DE");
decimal resultAmount = decimal.Parse(displayAmount, style, provider);
decimal expectedAmount = decimal.Parse($"{this.testAmount:n2}");
Assert.Equal(expectedAmount, resultAmount);
Assert.Equal(2, MockBitcoinMessageHandler.requestCount);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.