This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Saturday, 29 November 2025
عند اختبار الشفرة التي تستخدم HttpClient(ب) النهج التقليدي الذي ينطوي على الاستهزاء HttpMessageHandler استخدام أُطر مثل Moq. بينما يعمل هذا، يمكن أن يكون مشتقات، مراسم - هيافي، وبصراحة قبيحة إلى حد ما. هناك بديل أكثر نظافة: DelegatingHandler لإنشاء معالجات الاختبار التي تتصرف مثل نقاط نهاية HTTP الحقيقية.
في هذا المنصب سأريكم لماذا قد تتجاوزون السخريات كلياً وتستخدمون DelegatingHandler لرمز اختبار أكثر قابلية للقراءة، والحفظ، والتضاؤل.
هنا ما هو المثال 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);
وينطوي ذلك على عدة مسائل:
Protected() وقد عقد مؤتمراً بشأن ItExpr إلى SendAsync (أ) محمية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);
Muking Goking o Moq o Mucking Handler o |--------|-------------------|-------------------| | سطور الشفرة ▪ كثيرون / عدد قليل / | **** مستوى منخفض (ثقل ثقيل) مرتفع (ج ج فقط) | **** « فقير فقير وممت جدا » | جاري م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م_ م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م م (سحـر (سـحـر الأرجـح) / سهل (خطـة مـن خـلال) o | **** يَعْبَرُ يَعْبُ » | منح المنحة التعليمية □ ستيبر (إبهاسات مُقَع APISs) □ الحد الأدنى □ | المُمْكِلَات تحتاج إلى Muq لا شيء (بنين) o
ولكي نكون منصفين، هناك سيناريوهات يمكن أن يكون فيها الاستهزاء على غرار المغاربة مناسباً مع ذلك:
Verify() كان مفيداً للإتصالات التي تمّتبالنسبة للتحقق، يمكنك أن تضيفها إلى التفويض 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 اختبار:
في المرة القادمة عندما تصل إلى Mock<HttpMessageHandler>النظر فيما إذا كان بسيطاً DelegatingHandler إن مستقبلك الشخصي (وزملائك في الفريق) سيشكرك على شفرة الاختبار الأكثر نظافة وأكثر قابلية للصيانة.
انظر مشاريع الاختبار في هذا الحل لأمثلة العالم الحقيقي لهذا النمط في العمل.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.