وحدة الاختبار HttpClift مع عدم وجود (العربية (Arabic))

وحدة الاختبار HttpClift مع عدم وجود

Saturday, 29 November 2025

//

6 minute read

أولاً

عند اختبار الشفرة التي تستخدم HttpClient(ب) النهج التقليدي الذي ينطوي على الاستهزاء HttpMessageHandler استخدام أُطر مثل Moq. بينما يعمل هذا، يمكن أن يكون مشتقات، مراسم - هيافي، وبصراحة قبيحة إلى حد ما. هناك بديل أكثر نظافة: DelegatingHandler لإنشاء معالجات الاختبار التي تتصرف مثل نقاط نهاية HTTP الحقيقية.

في هذا المنصب سأريكم لماذا قد تتجاوزون السخريات كلياً وتستخدمون DelegatingHandler لرمز اختبار أكثر قابلية للقراءة، والحفظ، والتضاؤل.

المشكلة مع Missage Handler

هنا ما هو المثال HttpMessageHandler يَطْرقُ يَنْظرُ مثل مَع Muq:

var mockHandler = new Mock<HttpMessageHandler>();
mockHandler.Protected()
    .Setup<Task<HttpResponseMessage>>(
        "SendAsync",
        ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.ToString().Contains("api/send")),
        ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) =>
    {
        var requestBody = request.Content?.ReadAsStringAsync(cancellationToken).Result;
        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(requestBody ?? "No content", Encoding.UTF8, "application/json")
        };
    });

var client = new HttpClient(mockHandler.Object);

وينطوي ذلك على عدة مسائل:

  1. اكفر - الكثير من الكرات لما يجب أن يكون سلوكاً بسيطاً
  2. طريقة المز/ - أنت بحاجة - Protected() وقد عقد مؤتمراً بشأن ItExpr إلى SendAsync (أ) محمية
  3. & قراءة -منطق الاختبار الفعلي مدفون في مراسم التنصيب
  4. مُنكر - يحتاج كل اختبار إلى رمز تنصيب مماثل
  5. & شيء - من السهل الحصول على اسم هذا الطريق المستند إلى سلسلة السلسلة خطأ

المُفوض البديل البديل عن المَنْدَلِر

DelegatingHandler هو a بوصة net صنف لـ اعتراض HTTP طلبات قبل إلى شبكة هو وسطي مثل إعادة تجربة المعالجات و قطع الأشجار و توثيق المعالجات استخدام في الإنتاج.

هنا نفس الدالة باستخدام DelegatingHandler:

public class EchoHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var content = request.Content != null
            ? await request.Content.ReadAsStringAsync(cancellationToken)
            : "No content";

        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(content, Encoding.UTF8, "application/json")
        };
    }
}

مستخدماً عليه:

var client = new HttpClient(new EchoHandler());

هذا كل شيء، لا إطارات سخرية، لا طرق محمية للألعاب الرياضية،

مثال عالمي حقيقي : مَنْح خدمة الترجمة التحريرية

هذا مثال أكثر تطوراً من مُعالج إختبار خدمات الترجمة:

public class TranslateDelegatingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var absPath = request.RequestUri?.AbsolutePath;
        var method = request.Method;

        return absPath switch
        {
            "/translate" when method == HttpMethod.Post => await HandleTranslate(request),
            "/translate" => new HttpResponseMessage(HttpStatusCode.OK),
            "/health" => new HttpResponseMessage(HttpStatusCode.OK),
            _ => new HttpResponseMessage(HttpStatusCode.NotFound)
        };
    }

    private static async Task<HttpResponseMessage> HandleTranslate(HttpRequestMessage request)
    {
        var content = await request.Content!.ReadFromJsonAsync<TranslateRequest>();

        // Simulate error for specific test case
        if (content?.TargetLanguage == "xx")
            return new HttpResponseMessage(HttpStatusCode.InternalServerError);

        var response = new TranslateResponse("es", new[] { "Texto traducido" });
        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = JsonContent.Create(response)
        };
    }
}

هذا المعالج:

  • مسارات مختلفة إلى سلوكيات مختلفة
  • مُجَزّر طلب المحتوى إلى اتخاذ القرارات
  • خصائص
  • مُقَرَّرَةً وذايةً مُصَرَّفَةً ومُقَرَّرَةً وذايَةً مُصَرَّفَةً مُقَرَّرَةً وذايَةً مُوَثِّقَةً

الإعداد مع حقن الانعزال

أثناء استخدام IHttpClientFactory (الذي يجب أن تكون عليه)، دمج معالجات الاختبار هو واضح:

public static IServiceCollection SetupTestServices(DelegatingHandler handler)
{
    var services = new ServiceCollection();

    services.AddHttpClient<ITranslationService, TranslationService>(client =>
    {
        client.BaseAddress = new Uri("https://test.local");
    })
    .ConfigurePrimaryHttpMessageHandler(() => handler);

    return services;
}

ثم في اختباراتك:

[Fact]
public async Task Translate_ReturnsTranslatedText()
{
    var services = SetupTestServices(new TranslateDelegatingHandler());
    var provider = services.BuildServiceProvider();
    var service = provider.GetRequiredService<ITranslationService>();

    var result = await service.TranslateAsync("Hello", "es");

    Assert.Equal("Texto traducido", result);
}

[Fact]
public async Task Translate_InvalidLanguage_ThrowsException()
{
    var services = SetupTestServices(new TranslateDelegatingHandler());
    var provider = services.BuildServiceProvider();
    var service = provider.GetRequiredService<ITranslationService>();

    await Assert.ThrowsAsync<HttpRequestException>(
        () => service.TranslateAsync("Hello", "xx"));
}

الملاّئات المتحكّكة

لمزيد من المرونة، يمكنك إنشاء معالجات تقبل الإعداد:

public class ConfigurableHandler : DelegatingHandler
{
    private readonly Dictionary<string, Func<HttpRequestMessage, Task<HttpResponseMessage>>> _routes;

    public ConfigurableHandler()
    {
        _routes = new Dictionary<string, Func<HttpRequestMessage, Task<HttpResponseMessage>>>();
    }

    public ConfigurableHandler WithRoute(string path, HttpStatusCode status)
    {
        _routes[path] = _ => Task.FromResult(new HttpResponseMessage(status));
        return this;
    }

    public ConfigurableHandler WithRoute(string path, object responseBody)
    {
        _routes[path] = _ => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = JsonContent.Create(responseBody)
        });
        return this;
    }

    public ConfigurableHandler WithRoute(
        string path,
        Func<HttpRequestMessage, Task<HttpResponseMessage>> handler)
    {
        _routes[path] = handler;
        return this;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var path = request.RequestUri?.AbsolutePath ?? "";

        if (_routes.TryGetValue(path, out var handler))
            return await handler(request);

        return new HttpResponseMessage(HttpStatusCode.NotFound);
    }
}
var handler = new ConfigurableHandler()
    .WithRoute("/api/users", new[] { new User("Alice"), new User("Bob") })
    .WithRoute("/api/health", HttpStatusCode.OK)
    .WithRoute("/api/error", HttpStatusCode.InternalServerError);

var client = new HttpClient(handler);

لماذا يَختارُ تفويضَ Handler على Muks؟

Muking Goking o Moq o Mucking Handler o |--------|-------------------|-------------------| | سطور الشفرة ▪ كثيرون / عدد قليل / | **** مستوى منخفض (ثقل ثقيل) مرتفع (ج ج فقط) | **** « فقير فقير وممت جدا » | جاري م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م (سحـر (سـحـر الأرجـح) / سهل (خطـة مـن خـلال) o | **** يَعْبَرُ يَعْبُ » | منح المنحة التعليمية □ ستيبر (إبهاسات مُقَع APISs) □ الحد الأدنى □ | المُمْكِلَات تحتاج إلى Muq لا شيء (بنين) o

عندما يُشكِّل المصوِّك احساسا

ولكي نكون منصفين، هناك سيناريوهات يمكن أن يكون فيها الاستهزاء على غرار المغاربة مناسباً مع ذلك:

  1. الردود البسيطة لمرة واحدة - إذا كنت بحاجة إلى رد رد رد واحد مرة واحدة، في الخط الأول Muq قد يكون أسرع
  2. التحقق من التحقق فــي -الممق - Verify() كان مفيداً للإتصالات التي تمّت
  3. أمر الأمر - إذا كان فريقك لديه بالفعل بنية تحتية واسعة

بالنسبة للتحقق، يمكنك أن تضيفها إلى التفويض Handler أيضا:

public class VerifyingHandler : DelegatingHandler
{
    public List<HttpRequestMessage> ReceivedRequests { get; } = new();

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        ReceivedRequests.Add(request);
        return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
    }
}

ثالثاً - استنتاج

& بوصة DelegatingHandler لـ HttpClient اختبار:

  • رمز الاتفاق - لا يوجد احتفال إطاري
  • اختبارات ما قبل - مجرد فصول عادية من C##
  • **** - المشاركة في صفوف الامتحانات
  • مُحَججّز مُيسّى - وضع نقاط كسر، خطوة من خلال شفرة
  • مُصفّف - إنه مبني على شبكة الإنترنت

في المرة القادمة عندما تصل إلى Mock<HttpMessageHandler>النظر فيما إذا كان بسيطاً DelegatingHandler إن مستقبلك الشخصي (وزملائك في الفريق) سيشكرك على شفرة الاختبار الأكثر نظافة وأكثر قابلية للصيانة.

انظر مشاريع الاختبار في هذا الحل لأمثلة العالم الحقيقي لهذا النمط في العمل.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.