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

<datetime class="hidden">2025-11-29T07:00</datetime>

<!--category-- xUnit, Unit Testing, HttpClient -->
## أولاً

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

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

[TOC]

## المشكلة مع Missage Handler

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

```csharp
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`:

```csharp
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")
        };
    }
}
```

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

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

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

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

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

```csharp
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` (الذي يجب أن تكون عليه)، دمج معالجات الاختبار هو واضح:

```csharp
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;
}
```

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

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

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

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

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

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

```csharp
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 أيضا:

```csharp
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` إن مستقبلك الشخصي (وزملائك في الفريق) سيشكرك على شفرة الاختبار الأكثر نظافة وأكثر قابلية للصيانة.

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