← Барлық мақалаларға оралу

C# ішінде HttpClient mock жасау

Жарияланды

HttpClient арқылы external API calls жасайтын services үшін unit tests жазғанда, біздің simplified ArticleService example ішінде real API calls mocks-пен ауыстыру керек.

public class ArticleService(IHttpClientFactory httpClientFactory)
{
    private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;

    public async Task<Article?> GetLastArticle()
    {
        var client = _httpClientFactory.CreateClient("ArticleApi");

        using var response = await client.GetAsync("lastarticle");

        if (!response.IsSuccessStatusCode)
        {
            // Optionally log or handle the error here
            return null;
        }

        var responseJson = await response.Content.ReadAsStringAsync();
        var article = JsonSerializer.Deserialize<Article>(responseJson);

        return article;
    }
}

Мысалы NSubstitute арқылы IHttpClientFactory interface mock жасау alone жеткіліксіз. HttpMessageHandler-ден inherit ететін MockHttpMessageHandler class жасау керек.

image

internal class MockHttpMessageHandler(HttpResponseMessage responseMessage) : HttpMessageHandler
{
    private readonly HttpResponseMessage _responseMessage = responseMessage;

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        return Task.FromResult(_responseMessage);
    }
}

MockHttpMessageHandler constructor ішінде HttpResponseMessage қабылдайды, яғни mock-тен күтілетін message, және оны overridden SendAsync method ішінде жай return етеді.

image

Source: HTTP Message Handlers in ASP.NET Web API

Successful Response тестілеу

public class ApiServiceTests
{
    [Fact]
    public async Task GetLastArticle_ReturnsArticle()
    {
        // Arrange
        var expectedArticle = new Article { Id = 1, Title = "Test Title", Body = "Test Body" };
        var jsonResponse = JsonSerializer.Serialize(expectedArticle);
        var httpResponse = new HttpResponseMessage
        {
            StatusCode = HttpStatusCode.OK,
            Content = new StringContent(jsonResponse)
        };
        var mockHttpMessageHandler = new MockHttpMessageHandler(httpResponse);
        var httpClient = new HttpClient(mockHttpMessageHandler)
        {
            BaseAddress = new Uri("https://example.com/")
        };

        var mockHttpClientFactory = Substitute.For<IHttpClientFactory>();
        mockHttpClientFactory.CreateClient("ArticleApi").Returns(httpClient);

        var articleService = new ArticleService(mockHttpClientFactory);

        // Act
        var result = await articleService.GetLastArticle();

        // Assert
        Assert.NotNull(result);
        Assert.Equal(expectedArticle.Id, result.Id);
        Assert.Equal(expectedArticle.Title, result.Title);
    }
}

HttpClient жасағанда constructor ішіне MockHttpMessageHandler class instance береміз және осы HttpClient мәнін mockHttpClientFactory instance ішіндегі CreateClient method return value етеміз.

Mock-ты flexible ету

Mock-ты SendAsync method ішінде called болатын Func delegate арқылы more flexible етуге болады.

public class MockHttpMessageHandler(
    Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> sendAsyncFunc)
    : HttpMessageHandler
{
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>>
        _sendAsyncFunc = sendAsyncFunc;

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        return await _sendAsyncFunc(request, cancellationToken);
    }
}

Бұл жағдайда HTTP requests кезінде болатын exceptions тестілеуге болады.

[Fact]
public async Task GetLastArticle_ThrowsHttpRequestException()
{
    // Arrange
    var mockHttpMessageHandler = new MockHttpMessageHandler(
        (r, ct) => throw new HttpRequestException());
    var httpClient = new HttpClient(mockHttpMessageHandler)
    {
        BaseAddress = new Uri("https://example.com/")
    };

    var mockHttpClientFactory = Substitute.For<IHttpClientFactory>();
    mockHttpClientFactory.CreateClient("ArticleApi").Returns(httpClient);

    var articleService = new ArticleService(mockHttpClientFactory);

    // Act & Assert
    await Assert.ThrowsAsync<HttpRequestException>(() => articleService.GetLastArticle());
}

Нәтижесінде external dependencies қолданбай HttpClient mock жасаудың simple way аламыз, әрі expected HTTP responses flexible configuration жасауға болады.