यूनिट परीक्षणों में Mocking HttpClient


110

मेरे पास कुछ मुद्दे हैं जो मेरे कोड को इकाई परीक्षणों में उपयोग करने के लिए लपेटने की कोशिश कर रहे हैं। मुद्दे यह है। I का इंटरफ़ेस IHttpHandler है:

public interface IHttpHandler
{
    HttpClient client { get; }
}

और इसका उपयोग करने वाला वर्ग, HttpHandler:

public class HttpHandler : IHttpHandler
{
    public HttpClient client
    {
        get
        {
            return new HttpClient();
        }
    }
}

और फिर कनेक्शन वर्ग, जो क्लाइंट कार्यान्वयन को इंजेक्ट करने के लिए simpleIOC का उपयोग करता है:

public class Connection
{
    private IHttpHandler _httpClient;

    public Connection(IHttpHandler httpClient)
    {
        _httpClient = httpClient;
    }
}

और फिर मेरे पास एक इकाई परीक्षण परियोजना है जिसमें यह वर्ग है:

private IHttpHandler _httpClient;

[TestMethod]
public void TestMockConnection()
{
    var client = new Connection(_httpClient);

    client.doSomething();  

    // Here I want to somehow create a mock instance of the http client
    // Instead of the real one. How Should I approach this?     

}

अब जाहिर है कि मेरे पास कनेक्शन वर्ग में ऐसे तरीके होंगे जो मेरे बैक एंड से डेटा (JSON) को पुनः प्राप्त करेंगे। हालाँकि, मैं इस वर्ग के लिए इकाई परीक्षण लिखना चाहता हूं, और जाहिर है कि मैं असली बैक एंड के खिलाफ परीक्षण नहीं लिखना चाहता, बल्कि एक नकली। मैंने बड़ी सफलता के बिना इस पर एक अच्छा उत्तर देने की कोशिश की है। मैं इससे पहले भी मॉक का इस्तेमाल कर सकता हूं, लेकिन कभी भी httpClient जैसी चीज का इस्तेमाल नहीं कर सकता। मुझे इस समस्या से कैसे संपर्क करना चाहिए?

अग्रिम में धन्यवाद।


1
HttpClientअपने इंटरफ़ेस में ए एक्सपोज़ करना जहाँ समस्या है। आप अपने ग्राहक को HttpClientठोस वर्ग का उपयोग करने के लिए मजबूर कर रहे हैं । इसके बजाय, आप एक को बेनकाब करना चाहिए अमूर्त की HttpClient
माइक इज़ोन

क्या आप इसे और गहराई से समझा सकते हैं? मुझे कनेक्शन क्लासेस कंस्ट्रक्टर का निर्माण कैसे करना चाहिए क्योंकि मैं नहीं चाहता कि अन्य वर्गों में HttpClient की कोई निर्भरता कनेक्शन क्लास का उपयोग करें। उदाहरण के लिए मैं कनेक्शन के कंस्ट्रक्टर में कंफ़ेद्दी HttpClient पास नहीं करना चाहता, क्योंकि यह हर दूसरे वर्ग को बना देगा जो HttpClient के कनेक्शन पर निर्भर करता है?
tjugg

आपकी रुचि के बाहर, आपने क्या किया? जाहिरा तौर पर mockhttp कुछ SEO सुधार का उपयोग कर सकता है।
रिचर्ड शेजयले

@ माइक - जैसा कि मेरे उत्तर में बताया गया है, वास्तव में HttpClient को अमूर्त करने की कोई आवश्यकता नहीं है। यह पूरी तरह से परीक्षण योग्य है। मेरे पास कई परियोजनाएं हैं जिनमें इस पद्धति का उपयोग करके बैकेंड-कम टेस्ट सूट हैं।
रिचर्ड शेजय

जवाबों:


37

आपका इंटरफ़ेस कंक्रीट को उजागर करता है HttpClient वर्ग , इसलिए इस इंटरफ़ेस का उपयोग करने वाले किसी भी वर्ग को इसके साथ जोड़ा जाता है, इसका मतलब है कि इसका मजाक नहीं उड़ाया जा सकता है।

HttpClientकिसी भी इंटरफ़ेस से विरासत में नहीं मिलता है, इसलिए आपको अपना खुद का लिखना होगा। मेरा सुझाव है कि एक डेकोरेटर जैसा पैटर्न:

public interface IHttpHandler
{
    HttpResponseMessage Get(string url);
    HttpResponseMessage Post(string url, HttpContent content);
    Task<HttpResponseMessage> GetAsync(string url);
    Task<HttpResponseMessage> PostAsync(string url, HttpContent content);
}

और आपकी कक्षा इस तरह दिखाई देगी:

public class HttpClientHandler : IHttpHandler
{
    private HttpClient _client = new HttpClient();

    public HttpResponseMessage Get(string url)
    {
        return GetAsync(url).Result;
    }

    public HttpResponseMessage Post(string url, HttpContent content)
    {
        return PostAsync(url, content).Result;
    }

    public async Task<HttpResponseMessage> GetAsync(string url)
    {
        return await _client.GetAsync(url);
    }

    public async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
    {
        return await _client.PostAsync(url, content);
    }
}

इस सब में बिंदु यह है कि HttpClientHandlerअपना खुद का निर्माण करता है HttpClient, तो आप निश्चित रूप से कई कक्षाएं बना सकते हैं जो लागू करते हैंIHttpHandler विभिन्न तरीकों से होते हैं।

इस दृष्टिकोण के साथ मुख्य मुद्दा यह है कि आप एक वर्ग को प्रभावी ढंग से लिख रहे हैं जो सिर्फ दूसरी कक्षा में तरीकों को बुलाता है, हालांकि आप एक वर्ग बना सकते हैं इनहेरिट करता है HttpClient(देखें नकोसी का उदाहरण , यह मेरी तुलना में बहुत बेहतर दृष्टिकोण है)। जीवन बहुत आसान होगा यदि आपके HttpClientपास एक इंटरफ़ेस था जो आप नकली कर सकते थे, दुर्भाग्य से यह नहीं है।

यह उदाहरण हालांकि स्वर्णिम टिकट नहीं है। IHttpHandlerअभी भी निर्भर करता हैHttpResponseMessage , जो System.Net.Httpनाम स्थान से संबंधित है , इसलिए यदि आपको इसके अलावा अन्य कार्यान्वयन की आवश्यकता है HttpClient, तो आपको अपनी प्रतिक्रियाओं को HttpResponseMessageवस्तुओं में बदलने के लिए किसी प्रकार का मानचित्रण करना होगा । यह निश्चित रूप से केवल एक समस्या है अगर आपको कई कार्यान्वयन का उपयोग करने की आवश्यकता है , IHttpHandlerलेकिन ऐसा नहीं लगता है कि आप ऐसा करते हैं तो यह दुनिया का अंत नहीं है, लेकिन इसके बारे में सोचने के लिए कुछ है।

वैसे भी, आप केवल IHttpHandlerठोस HttpClientवर्ग की चिंता किए बिना उसका मज़ाक उड़ा सकते हैं क्योंकि इसे दूर कर दिया गया है।

मैं गैर-एसिंक्स विधियों का परीक्षण करने की सलाह देता हूं , क्योंकि ये अभी भी अतुल्यकालिक विधियों को कहते हैं, लेकिन यूनिट परीक्षण अतुल्यकालिक तरीकों के बारे में चिंता करने की परेशानी के बिना, यहां देखें


यह वास्तव में मेरे सवाल का जवाब देता है। Nkosis उत्तर भी सही है इसलिए मुझे यकीन नहीं है कि मुझे उत्तर के रूप में स्वीकार करना चाहिए, लेकिन मैं इस एक के साथ जाऊंगा। इस प्रयास के लिए दोनों को धन्यवाद
tjugg

@tjugg खुशी मदद करने के लिए यदि आपने उन्हें उपयोगी पाया है, तो जवाबों को वोट करने के लिए स्वतंत्र महसूस करें।
नकोसी

3
ध्यान देने योग्य बात यह है कि इस उत्तर और नाकोसी के बीच मुख्य अंतर यह है कि यह बहुत पतला है। पतली शायद एक विनम्र वस्तु के
बेन आरोनसन

227

HttpClient की व्यापकता में निहित है HttpMessageHandler कंस्ट्रक्टर को पास करने । इसका इरादा प्लेटफ़ॉर्म विशिष्ट कार्यान्वयन की अनुमति देना है, लेकिन आप इसे मॉक भी कर सकते हैं। HttpClient के लिए डेकोरेटर रैपर बनाने की कोई आवश्यकता नहीं है।

यदि आप Moq का उपयोग करने के लिए एक DSL पसंद करते हैं, मेरे पास GitHub / Nuget पर एक पुस्तकालय है जो चीजों को थोड़ा आसान बनाता है: https://github.com/richardszalay/mockhttp

var mockHttp = new MockHttpMessageHandler();

// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localost/api/user/*")
        .Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON

// Inject the handler or client into your application code
var client = new HttpClient(mockHttp);

var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;

var json = await response.Content.ReadAsStringAsync();

// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}

1
तो मैं सिर्फ MockHttpMessageHandler संदेशवाहक Httphandler वर्ग के रूप में पास करूंगा? या आपने इसे अपनी परियोजनाओं में कैसे लागू किया है
tjugg

2
महान जवाब और कुछ जो मुझे शुरू में नहीं पता था। HttpClient के साथ काम करना इतना बुरा नहीं है।
बीलर

6
उन लोगों के लिए जो ग्राहक को इंजेक्शन लगाने से निपटना नहीं चाहते हैं, लेकिन फिर भी आसान परीक्षण करना चाहते हैं, इसे हासिल करना तुच्छ है। बस एक फ़ील्ड के var client = new HttpClient()साथ बदलें var client = ClientFactory()और सेट करें internal static Func<HttpClient> ClientFactory = () => new HttpClient();और परीक्षण स्तर पर आप इस फ़ील्ड को फिर से लिख सकते हैं।
क्रिस मैरिसिक

3
@ChrisMarisic आप इंजेक्शन को बदलने के लिए सेवा स्थान का एक रूप सुझा रहे हैं। सेवा स्थान एक प्रसिद्ध एंटी-पैटर्न है इसलिए इमहो इंजेक्शन बेहतर है।
मारियोडीस

2
@MarioDS और इसकी परवाह किए बिना, आपको HttpClient उदाहरण को बिल्कुल भी इंजेक्ट नहीं करना चाहिए । आप इस तो आप एक इंजेक्शन लगाने के लिए किया जाना चाहिए constructer इंजेक्शन के प्रयोग पर मृत सेट कर रहे हैं HttpClientFactoryके रूप में Func<HttpClient>। यह देखते हुए कि मैं HttpClient को विशुद्ध रूप से एक कार्यान्वयन विवरण के रूप में देखता हूं और एक निर्भरता नहीं, मैं स्टैटिक्स का उपयोग वैसे ही करूंगा जैसे मैंने ऊपर दिखाया था। मैं आंतरिक रूप से हेरफेर करने वाले परीक्षणों से पूरी तरह ठीक हूं। अगर मुझे प्योर-इस्म की परवाह है तो मैं फुल सर्वर खड़ा करूँगा और लाइव कोड पाथ टेस्ट करूँगा। किसी भी प्रकार के मॉक का उपयोग करने का मतलब है कि आप व्यवहार के सन्निकटन को स्वीकार करते हैं न कि वास्तविक व्यवहार को।
क्रिस मैरिसिक

39

मैं कुछ अन्य जवाबों से सहमत हूं कि सबसे अच्छा तरीका यह है कि HttpMessageHandler को रैप करने के बजाय HttpClient को मॉक करें। यह उत्तर इस मायने में अनूठा है कि यह अभी भी HttpClient को इंजेक्शन देता है, जिससे यह एक सिंगलटन हो सकता है या निर्भरता इंजेक्शन के साथ प्रबंधित किया जा सकता है।

"HttpClient को एक बार इंस्टेंटिएट करने और एप्लिकेशन के पूरे जीवन में फिर से उपयोग करने का इरादा है।" ( स्रोत )

Mocking HttpMessageHandler थोड़ा मुश्किल हो सकता है क्योंकि SendAsync सुरक्षित है। यहाँ एक पूर्ण उदाहरण है, xunit और Moq का उपयोग करते हुए।

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
using Xunit;
// Use nuget to install xunit and Moq

namespace MockHttpClient {
    class Program {
        static void Main(string[] args) {
            var analyzer = new SiteAnalyzer(Client);
            var size = analyzer.GetContentSize("http://microsoft.com").Result;
            Console.WriteLine($"Size: {size}");
        }

        private static readonly HttpClient Client = new HttpClient(); // Singleton
    }

    public class SiteAnalyzer {
        public SiteAnalyzer(HttpClient httpClient) {
            _httpClient = httpClient;
        }

        public async Task<int> GetContentSize(string uri)
        {
            var response = await _httpClient.GetAsync( uri );
            var content = await response.Content.ReadAsStringAsync();
            return content.Length;
        }

        private readonly HttpClient _httpClient;
    }

    public class SiteAnalyzerTests {
        [Fact]
        public async void GetContentSizeReturnsCorrectLength() {
            // Arrange
            const string testContent = "test content";
            var mockMessageHandler = new Mock<HttpMessageHandler>();
            mockMessageHandler.Protected()
                .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                .ReturnsAsync(new HttpResponseMessage {
                    StatusCode = HttpStatusCode.OK,
                    Content = new StringContent(testContent)
                });
            var underTest = new SiteAnalyzer(new HttpClient(mockMessageHandler.Object));

            // Act
            var result = await underTest.GetContentSize("http://anyurl");

            // Assert
            Assert.Equal(testContent.Length, result);
        }
    }
}

1
मुझे यह वास्तव में पसंद आया। mockMessageHandler.Protected()हत्यारा था। इस उदाहरण के लिए धन्यवाद। यह स्रोत को संशोधित किए बिना परीक्षण लिखने की अनुमति देता है।
टायरियन

1
FYI करें, Moq 4.8 संरक्षित सदस्यों के जोरदार टाइपिंग का समर्थन करता है - github.com/Moq/moq4/wiki/Quickstart
रिचर्ड सज़ले

2
यह बहुत अच्छा लग रहा है। इसके अलावा Moq ReturnsAsync तो कोड की तरह लग रहे हैं का समर्थन करता है.ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})
Kord

धन्यवाद @kord, मैंने कहा कि उत्तर के लिए
PointZeroTwo

3
क्या यह सत्यापित करने का कोई तरीका है कि "सैंडएस्क्यूं" को कुछ मापदंडों के साथ बुलाया गया था? मैंने उपयोग करने की कोशिश की है ... संरक्षित ()। सत्यापित करें (...), लेकिन ऐसा लगता है कि यह async विधियों के साथ काम नहीं करता है।
Rroman

29

यह एक सामान्य प्रश्न है, और मैं बहुत हद तक उस तरफ था जो HttpClient का मजाक उड़ाने की क्षमता चाहता था, लेकिन मुझे लगता है कि मुझे आखिरकार यह एहसास हुआ कि आपको HttpClient का मजाक नहीं उड़ाना चाहिए। ऐसा करना तर्कसंगत लगता है, लेकिन मुझे लगता है कि हम उन चीजों से दिमाग लगा रहे हैं जो हम ओपन सोर्स लाइब्रेरी में देखते हैं।

हम अक्सर "ग्राहकों" को देखते हैं कि हम अपने कोड में मजाक करते हैं ताकि हम अलगाव में परीक्षण कर सकें, इसलिए हम स्वचालित रूप से HttpClient के लिए एक ही सिद्धांत को लागू करने का प्रयास करते हैं। HttpClient वास्तव में बहुत कुछ करता है; आप इसे HttpMessageHandler के प्रबंधक के रूप में सोच सकते हैं, इसलिए आप इसका मजाक नहीं उड़ाते, और यही कारण है कि अभी भी कोई इंटरफ़ेस नहीं है। वह हिस्सा जिसे आप इकाई परीक्षण के लिए वास्तव में रुचि रखते हैं, या अपनी सेवाओं को डिजाइन करना चाहते हैं, यहां तक ​​कि HttpMessageHandler भी है, जो कि प्रतिक्रिया देता है, और आप इसका मजाक उड़ा सकते हैं

यह भी इंगित करने योग्य है कि आपको शायद HttpClient का इलाज एक बड़े सौदे की तरह शुरू करना चाहिए। उदाहरण के लिए: नए HttpClients के अपने instatiating को कम से कम रखें। उनका पुन: उपयोग करें, वे पुन: उपयोग करने के लिए डिज़ाइन किए गए हैं और यदि आप करते हैं तो एक बकवास टन कम संसाधनों का उपयोग करें। यदि आप इसे बड़े सौदे की तरह मानने लगते हैं, तो इसे गलत मान लेना ज्यादा गलत होगा और अब मैसेज हैंडलर वह चीज बनने लगेगा जो आप इंजेक्ट कर रहे हैं, क्लाइंट नहीं।

दूसरे शब्दों में, क्लाइंट के बजाय हैंडलर के चारों ओर अपनी निर्भरता डिज़ाइन करें। इससे भी बेहतर, अमूर्त "सेवाएं" जो कि HttpClient का उपयोग करती हैं जो आपको एक हैंडलर को इंजेक्ट करने की अनुमति देती हैं, और इसका उपयोग आपकी इंजेक्शन निर्भरता के रूप में करती हैं। फिर अपने परीक्षणों में, आप अपने परीक्षणों की स्थापना के लिए प्रतिक्रिया को नियंत्रित करने के लिए हैंडलर को नकली कर सकते हैं।

रैपिंग HttpClient समय का एक पागल बेकार है।

अपडेट: जोशुआ डूमस का उदाहरण देखें। यह वही है जो मैं सुझा रहा हूं।


17

भी टिप्पणी में उल्लेख किया है कि आप की जरूरत है सार दूरHttpClient इतनी के रूप में इसे करने के लिए मिलकर करने के लिए नहीं। मैंने अतीत में भी कुछ ऐसा ही किया है। मैं जो करने की कोशिश कर रहा हूं, उसके साथ जो मैंने किया था, उसे करने की कोशिश करूंगा।

पहले देखो HttpClient कक्षा को देखें और यह तय करें कि किस कार्यक्षमता की जरूरत है।

यहाँ एक संभावना है:

public interface IHttpClient {
    System.Threading.Tasks.Task<T> DeleteAsync<T>(string uri) where T : class;
    System.Threading.Tasks.Task<T> DeleteAsync<T>(Uri uri) where T : class;
    System.Threading.Tasks.Task<T> GetAsync<T>(string uri) where T : class;
    System.Threading.Tasks.Task<T> GetAsync<T>(Uri uri) where T : class;
    System.Threading.Tasks.Task<T> PostAsync<T>(string uri, object package);
    System.Threading.Tasks.Task<T> PostAsync<T>(Uri uri, object package);
    System.Threading.Tasks.Task<T> PutAsync<T>(string uri, object package);
    System.Threading.Tasks.Task<T> PutAsync<T>(Uri uri, object package);
}

जैसा कि पहले कहा गया था फिर से विशेष प्रयोजनों के लिए था। मैंने पूरी तरह से कुछ भी निपटने के लिए सबसे अधिक निर्भरता को खत्म कर दिया HttpClientऔर जो मैं चाहता था उस पर ध्यान केंद्रित किया। आपको मूल्यांकन करना चाहिए कि आप किस तरह सार करना चाहते हैंHttpClient केवल आवश्यक कार्यक्षमता प्रदान करने के लिए करना चाहते हैं।

यह अब आपको केवल वही नकल करने की अनुमति देगा जो परीक्षण करने की आवश्यकता है।

मैं भी IHttpHandlerपूरी तरह से दूर करने और HttpClientअमूर्त का उपयोग करने की सलाह दूंगा IHttpClient। लेकिन मैं अभी नहीं उठा रहा हूं क्योंकि आप अपने हैंडलर इंटरफेस के शरीर को एब्सट्रैक्ट क्लाइंट के सदस्यों से बदल सकते हैं।

इसके लागू होने के IHttpClientबाद HttpClientउस चीज़ के लिए एक वास्तविक / ठोस या किसी अन्य वस्तु को लपेटने / अनुकूलित करने के लिए इस्तेमाल किया जा सकता है, जिसका उपयोग HTTP अनुरोधों को करने के लिए किया जा सकता है जैसा कि आप वास्तव में चाहते थे एक सेवा थी जो उस कार्यक्षमता को एप्लाइड के रूप में प्रदान करती थी।HttpClient विशेष । अमूर्त का उपयोग करना एक साफ (मेरी राय) और एसओएलआईडी दृष्टिकोण है और फ्रेमवर्क परिवर्तन के रूप में आपको किसी अन्य चीज के लिए अंतर्निहित क्लाइंट को स्विच करने की आवश्यकता होने पर अपने कोड को अधिक बनाए रखने योग्य बना सकता है।

यहाँ एक स्निपेट है कि कैसे एक कार्यान्वयन किया जा सकता है।

/// <summary>
/// HTTP Client adaptor wraps a <see cref="System.Net.Http.HttpClient"/> 
/// that contains a reference to <see cref="ConfigurableMessageHandler"/>
/// </summary>
public sealed class HttpClientAdaptor : IHttpClient {
    HttpClient httpClient;

    public HttpClientAdaptor(IHttpClientFactory httpClientFactory) {
        httpClient = httpClientFactory.CreateHttpClient(**Custom configurations**);
    }

    //...other code

     /// <summary>
    ///  Send a GET request to the specified Uri as an asynchronous operation.
    /// </summary>
    /// <typeparam name="T">Response type</typeparam>
    /// <param name="uri">The Uri the request is sent to</param>
    /// <returns></returns>
    public async System.Threading.Tasks.Task<T> GetAsync<T>(Uri uri) where T : class {
        var result = default(T);
        //Try to get content as T
        try {
            //send request and get the response
            var response = await httpClient.GetAsync(uri).ConfigureAwait(false);
            //if there is content in response to deserialize
            if (response.Content.Headers.ContentLength.GetValueOrDefault() > 0) {
                //get the content
                string responseBodyAsText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                //desrialize it
                result = deserializeJsonToObject<T>(responseBodyAsText);
            }
        } catch (Exception ex) {
            Log.Error(ex);
        }
        return result;
    }

    //...other code
}

जैसा कि आप ऊपर दिए गए उदाहरण में देख सकते हैं, आमतौर पर उपयोग करने के साथ जुड़ा हुआ भारी उठाने का एक बहुत HttpClientअमूर्त के पीछे छिपा हुआ है।

आप कनेक्शन वर्ग तो अमूर्त ग्राहक के साथ इंजेक्ट किया जा सकता है

public class Connection
{
    private IHttpClient _httpClient;

    public Connection(IHttpClient httpClient)
    {
        _httpClient = httpClient;
    }
}

आपका परीक्षण तब आपके SUT के लिए आवश्यक चीज़ों का मज़ाक उड़ा सकता है

private IHttpClient _httpClient;

[TestMethod]
public void TestMockConnection()
{
    SomeModelObject model = new SomeModelObject();
    var httpClientMock = new Mock<IHttpClient>();
    httpClientMock.Setup(c => c.GetAsync<SomeModelObject>(It.IsAny<string>()))
        .Returns(() => Task.FromResult(model));

    _httpClient = httpClientMock.Object;

    var client = new Connection(_httpClient);

    // Assuming doSomething uses the client to make
    // a request for a model of type SomeModelObject
    client.doSomething();  
}

यह उत्तर है। ऊपर एक अमूर्त HttpClientऔर एक एडाप्टर का उपयोग कर अपने विशिष्ट उदाहरण बनाने के लिए HttpClientFactory। ऐसा करने से HTTP अनुरोध तुच्छ से परे तर्क का परीक्षण करता है, जो यहां लक्ष्य है।
pimbrouwers

13

अन्य उत्तरों के आधार पर, मैं इस कोड का सुझाव देता हूं, जिसमें कोई बाहरी निर्भरता नहीं है:

[TestClass]
public class MyTestClass
{
    [TestMethod]
    public async Task MyTestMethod()
    {
        var httpClient = new HttpClient(new MockHttpMessageHandler());

        var content = await httpClient.GetStringAsync("http://some.fake.url");

        Assert.AreEqual("Content as string", content);
    }
}

public class MockHttpMessageHandler : HttpMessageHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var responseMessage = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("Content as string")
        };

        return await Task.FromResult(responseMessage);
    }
}

4
आप अपने मॉक का प्रभावी परीक्षण कर रहे हैं। एक नकली की असली शक्ति यह है कि आप उम्मीदों को सेट कर सकते हैं और प्रत्येक परीक्षण में इसे बदल सकते हैं। तथ्य यह है कि आपको कुछ HttpMessageHandlerखुद को लागू करना पड़ता है जो असंभव के बगल में करता है - और आपको इसलिए क्योंकि तरीके हैं protected internal
मारियोडीस

3
@ मेरा मानना ​​है कि बिंदु यह है कि आप HTTP प्रतिक्रिया का मजाक उड़ा सकते हैं ताकि आप बाकी कोड का परीक्षण कर सकें। यदि आप एक ऐसी फैक्ट्री को इंजेक्ट करते हैं जो कि HttpClient मिलती है, तो परीक्षणों में आप इस HttpClient की आपूर्ति कर सकते हैं।
chris31389

13

मुझे लगता है कि मुद्दा यह है कि आपने इसे थोड़ा उल्टा कर दिया है।

public class AuroraClient : IAuroraClient
{
    private readonly HttpClient _client;

    public AuroraClient() : this(new HttpClientHandler())
    {
    }

    public AuroraClient(HttpMessageHandler messageHandler)
    {
        _client = new HttpClient(messageHandler);
    }
}

यदि आप ऊपर की कक्षा को देखते हैं, तो मुझे लगता है कि यह वही है जो आप चाहते हैं। Microsoft ग्राहक को इष्टतम प्रदर्शन के लिए जीवित रखने की सलाह देता है, इसलिए इस प्रकार की संरचना आपको ऐसा करने की अनुमति देती है। इसके अलावा HttpMessageHandler एक अमूर्त वर्ग है और इसलिए इसका उपयोग करने योग्य है। आपकी परीक्षा विधि फिर इस तरह दिखाई देगी:

[TestMethod]
public void TestMethod1()
{
    // Arrange
    var mockMessageHandler = new Mock<HttpMessageHandler>();
    // Set up your mock behavior here
    var auroraClient = new AuroraClient(mockMessageHandler.Object);
    // Act
    // Assert
}

यह आपको HttpClient के व्यवहार का मजाक उड़ाते हुए अपने तर्क का परीक्षण करने की अनुमति देता है।

क्षमा करें दोस्तों, यह लिखने और इसे स्वयं आज़माने के बाद, मुझे एहसास हुआ कि आप HttpMessageHandler पर संरक्षित विधियों का मजाक नहीं उड़ा सकते। मैंने बाद में एक उचित नकली के इंजेक्शन के लिए अनुमति देने के लिए निम्नलिखित कोड जोड़ा।

public interface IMockHttpMessageHandler
{
    Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}

public class MockHttpMessageHandler : HttpMessageHandler
{
    private readonly IMockHttpMessageHandler _realMockHandler;

    public MockHttpMessageHandler(IMockHttpMessageHandler realMockHandler)
    {
        _realMockHandler = realMockHandler;
    }

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

इस के साथ लिखे गए परीक्षण तब कुछ इस तरह दिखते हैं:

[TestMethod]
public async Task GetProductsReturnsDeserializedXmlXopData()
{
    // Arrange
    var mockMessageHandler = new Mock<IMockHttpMessageHandler>();
    // Set up Mock behavior here.
    var client = new AuroraClient(new MockHttpMessageHandler(mockMessageHandler.Object));
    // Act
    // Assert
}

9

मेरे एक सहकर्मी ने देखा कि अधिकांश HttpClientविधियाँ SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)हुड के नीचे कॉल करती हैं, जो कि एक आभासी विधि है HttpMessageInvoker:

अब तक मॉक आउट करने का सबसे आसान तरीका HttpClientउस विशेष विधि का केवल मजाक करना था:

var mockClient = new Mock<HttpClient>();
mockClient.Setup(client => client.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>())).ReturnsAsync(_mockResponse.Object);

और आपका कोड HttpClientएक नियमित सहित, क्लास विधियों के अधिकांश (लेकिन सभी नहीं) को कॉल कर सकता है

httpClient.SendAsync(req)

यहाँ देखें पुष्टि करने के लिए https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs


1
यह किसी भी कोड के लिए काम नहीं करता है जो SendAsync(HttpRequestMessage)सीधे कॉल करता है। यदि आप इस सुविधा फ़ंक्शन का उपयोग नहीं करने के लिए अपने कोड को संशोधित कर सकते हैं, तो सीधे ओवरराइड करके HttpClient का मज़ाक उड़ाना SendAsyncवास्तव में मुझे मिला सबसे साफ समाधान है।
डायलन निकोलसन

8

एक विकल्प स्टब एचटीटीपी सर्वर को सेटअप करना होगा जो अनुरोध यूआरएल से मेल खाते पैटर्न के आधार पर डिब्बाबंद प्रतिक्रियाएं देता है, जिसका अर्थ है कि आप वास्तविक एचटीटीपी अनुरोधों का परीक्षण नहीं करते हैं। ऐतिहासिक रूप से इसने महत्वपूर्ण महत्वपूर्ण प्रयास किए होंगे और यूनिट परीक्षण के लिए विचार किए जाने की गति धीमी होगी, हालांकि ओएसएस लाइब्रेरी वायरमॉक.नेट का उपयोग करना आसान है और बहुत सारे परीक्षणों के साथ तेजी से चलाया जा सकता है इसलिए विचार करने लायक हो सकता है। सेटअप कोड की कुछ पंक्तियाँ है:

var server = FluentMockServer.Start();
server.Given(
      Request.Create()
      .WithPath("/some/thing").UsingGet()
   )
   .RespondWith(
       Response.Create()
       .WithStatusCode(200)
       .WithHeader("Content-Type", "application/json")
       .WithBody("{'attr':'value'}")
   );

आप यहां परीक्षणों में वायरमॉक का उपयोग करने के बारे में अधिक विवरण और मार्गदर्शन पा सकते हैं


8

यहां एक सरल उपाय है, जिसने मेरे लिए अच्छा काम किया है।

मॉक मॉकिंग लाइब्रेरी का उपयोग करना।

// ARRANGE
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
   .Protected()
   // Setup the PROTECTED method to mock
   .Setup<Task<HttpResponseMessage>>(
      "SendAsync",
      ItExpr.IsAny<HttpRequestMessage>(),
      ItExpr.IsAny<CancellationToken>()
   )
   // prepare the expected response of the mocked http call
   .ReturnsAsync(new HttpResponseMessage()
   {
      StatusCode = HttpStatusCode.OK,
      Content = new StringContent("[{'id':1,'value':'1'}]"),
   })
   .Verifiable();

// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
   BaseAddress = new Uri("http://test.com/"),
};

var subjectUnderTest = new MyTestClass(httpClient);

// ACT
var result = await subjectUnderTest
   .GetSomethingRemoteAsync('api/test/whatever');

// ASSERT
result.Should().NotBeNull(); // this is fluent assertions here...
result.Id.Should().Be(1);

// also check the 'http' call was like we expected it
var expectedUri = new Uri("http://test.com/api/test/whatever");

handlerMock.Protected().Verify(
   "SendAsync",
   Times.Exactly(1), // we expected a single external request
   ItExpr.Is<HttpRequestMessage>(req =>
      req.Method == HttpMethod.Get  // we expected a GET request
      && req.RequestUri == expectedUri // to this uri
   ),
   ItExpr.IsAny<CancellationToken>()
);

स्रोत: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/


मैंने भी इसे सक्सेसफुल इस्तेमाल किया है। मैं अभी तक aohter nuget निर्भरता में dfragging करने के लिए पसंद करते हैं और आप वास्तव में हुड के नीचे क्या हो रहा है के बारे में थोड़ा और अधिक सीखते हैं। अच्छी बात यह है कि अधिकांश तरीकों का उपयोग SendAsyncवैसे भी किया जाता है, इसलिए अतिरिक्त सेटअप की आवश्यकता नहीं होती है।
स्टीव पेटीफ़र

4

मैं कई उत्तरों से आश्वस्त नहीं हूँ।

सबसे पहले, कल्पना करें कि आप एक विधि का परीक्षण करना चाहते हैं जो उपयोग करता है HttpClient। आपको HttpClientअपने कार्यान्वयन में सीधे तात्कालिक नहीं होना चाहिए । आपको अपने लिए एक उदाहरण प्रदान करने की जिम्मेदारी के साथ एक कारखाने को इंजेक्ट करना चाहिए HttpClient। इस तरह आप बाद में उस कारखाने का मजाक उड़ा सकते हैं और जो HttpClientचाहें वापस लौट सकते हैं (जैसे: एक नकली HttpClientऔर असली नहीं)।

तो, आप निम्नलिखित की तरह एक कारखाना होगा:

public interface IHttpClientFactory
{
    HttpClient Create();
}

और एक कार्यान्वयन:

public class HttpClientFactory
    : IHttpClientFactory
{
    public HttpClient Create()
    {
        var httpClient = new HttpClient();
        return httpClient;
    }
}

बेशक आपको इस कार्यान्वयन में अपने IoC कंटेनर में पंजीकरण करना होगा। यदि आप ऑटोफेक का उपयोग करते हैं तो यह कुछ इस तरह होगा:

builder
    .RegisterType<IHttpClientFactory>()
    .As<HttpClientFactory>()
    .SingleInstance();

अब आपके पास एक उचित और परीक्षण योग्य कार्यान्वयन होगा। कल्पना करें कि आपका तरीका कुछ इस तरह है:

public class MyHttpClient
    : IMyHttpClient
{
    private readonly IHttpClientFactory _httpClientFactory;

    public SalesOrderHttpClient(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<string> PostAsync(Uri uri, string content)
    {
        using (var client = _httpClientFactory.Create())
        {
            var clientAddress = uri.GetLeftPart(UriPartial.Authority);
            client.BaseAddress = new Uri(clientAddress);
            var content = new StringContent(content, Encoding.UTF8, "application/json");
            var uriAbsolutePath = uri.AbsolutePath;
            var response = await client.PostAsync(uriAbsolutePath, content);
            var responseJson = response.Content.ReadAsStringAsync().Result;
            return responseJson;
        }
    }
}

अब परीक्षण हिस्सा है। HttpClientफैली हुई है HttpMessageHandler, जो अमूर्त है। चलो एक "मॉक" बनाते हैं जो HttpMessageHandlerएक प्रतिनिधि को स्वीकार करता है ताकि जब हम मॉक का उपयोग करें तो हम प्रत्येक परीक्षण के लिए प्रत्येक व्यवहार को भी सेट कर सकें।

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

    public MockHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> sendAsyncFunc)
    {
        _sendAsyncFunc = sendAsyncFunc;
    }

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

और अब, और Moq (और फ़्लुएंटसर्विज़न, एक पुस्तकालय जो इकाई परीक्षणों को और अधिक पठनीय बनाता है) की मदद से, हमारे पास इकाई विधि का उपयोग करने के लिए आवश्यक सब कुछ है जो हमारे पोस्टएज़्यूनिक का उपयोग करता है HttpClient

public static class PostAsyncTests
{
    public class Given_A_Uri_And_A_JsonMessage_When_Posting_Async
        : Given_WhenAsync_Then_Test
    {
        private SalesOrderHttpClient _sut;
        private Uri _uri;
        private string _content;
        private string _expectedResult;
        private string _result;

        protected override void Given()
        {
            _uri = new Uri("http://test.com/api/resources");
            _content = "{\"foo\": \"bar\"}";
            _expectedResult = "{\"result\": \"ok\"}";

            var httpClientFactoryMock = new Mock<IHttpClientFactory>();
            var messageHandlerMock =
                new MockHttpMessageHandler((request, cancellation) =>
                {
                    var responseMessage =
                        new HttpResponseMessage(HttpStatusCode.Created)
                        {
                            Content = new StringContent("{\"result\": \"ok\"}")
                        };

                    var result = Task.FromResult(responseMessage);
                    return result;
                });

            var httpClient = new HttpClient(messageHandlerMock);
            httpClientFactoryMock
                .Setup(x => x.Create())
                .Returns(httpClient);

            var httpClientFactory = httpClientFactoryMock.Object;

            _sut = new SalesOrderHttpClient(httpClientFactory);
        }

        protected override async Task WhenAsync()
        {
            _result = await _sut.PostAsync(_uri, _content);
        }


        [Fact]
        public void Then_It_Should_Return_A_Valid_JsonMessage()
        {
            _result.Should().BeEquivalentTo(_expectedResult);
        }
    }
}

जाहिर है यह परीक्षण मूर्खतापूर्ण है, और हम वास्तव में हमारे नकली का परीक्षण कर रहे हैं। लेकिन आप विचार समझ गये। आपको अपने कार्यान्वयन के आधार पर सार्थक तर्क का परीक्षण करना चाहिए जैसे कि ।।

  • यदि प्रतिक्रिया की कोड स्थिति 201 नहीं है, तो क्या इसे अपवाद फेंकना चाहिए?
  • यदि प्रतिक्रिया पाठ को पार्स नहीं किया जा सकता है, तो क्या होना चाहिए?
  • आदि।

इस उत्तर का उद्देश्य कुछ ऐसा परीक्षण करना था जो HttpClient का उपयोग करता है और ऐसा करने के लिए यह एक अच्छा साफ तरीका है।


4

पार्टी में शामिल होने में थोड़ी देर हो गई, लेकिन मुझे वायरमॉकिंग ( https://github.com/WireMock-Net/AVIock.Net) का उपयोग करना पसंद है जब भी संभव हो, डाउनस्ट्रीम REST निर्भरता के साथ डॉटनेट कोर माइक्रोसर्विस के एकीकरण में संभव हो तो ) ।

एक TestHttpClientFactory लागू करके IHttpClientFactory का विस्तार करते हुए हम विधि को ओवरराइड कर सकते हैं

HttpClient CreateClient (स्ट्रिंग नाम)

इसलिए अपने ऐप के भीतर नामित ग्राहकों का उपयोग करते समय आप अपने वायरमॉक को वायटकेट क्लाइंट को वापस करने के नियंत्रण में होते हैं।

इस दृष्टिकोण के बारे में अच्छी बात यह है कि आप जिस एप्लिकेशन का परीक्षण कर रहे हैं, उसके भीतर कुछ भी नहीं बदल रहा है, और आपकी सेवा के लिए एक वास्तविक REST अनुरोध करने और जोसन (या जो भी) वास्तविक डाउनस्ट्रीम अनुरोध को वापस करना चाहिए, पाठ्यक्रम एकीकरण परीक्षण सक्षम करता है। यह आपके एप्लिकेशन में संक्षिप्त परीक्षण और जितना संभव हो उतना कम मजाक उड़ाता है।

    public class TestHttpClientFactory : IHttpClientFactory 
{
    public HttpClient CreateClient(string name)
    {
        var httpClient = new HttpClient
        {
            BaseAddress = new Uri(G.Config.Get<string>($"App:Endpoints:{name}"))
            // G.Config is our singleton config access, so the endpoint 
            // to the running wiremock is used in the test
        };
        return httpClient;
    }
}

तथा

// in bootstrap of your Microservice
IHttpClientFactory factory = new TestHttpClientFactory();
container.Register<IHttpClientFactory>(factory);

2

चूंकि सभी को निष्पादित करने के लिए विधि का HttpClientउपयोग करें , आप विधि और नकली का उपयोग कर सकते हैंSendAsyncHTTP Requestsoverride SendAsyncHttpClient

उस रैप बनाने के HttpClientलिए interfaceनीचे कुछ पसंद है

public interface IServiceHelper
{
    HttpClient GetClient();
}

फिर interfaceअपनी सेवा में निर्भरता इंजेक्शन के लिए ऊपर का उपयोग करें, नीचे नमूना

public class SampleService
{
    private readonly IServiceHelper serviceHelper;

    public SampleService(IServiceHelper serviceHelper)
    {
        this.serviceHelper = serviceHelper;
    }

    public async Task<HttpResponseMessage> Get(int dummyParam)
    {
        try
        {
            var dummyUrl = "http://www.dummyurl.com/api/controller/" + dummyParam;
            var client = serviceHelper.GetClient();
            HttpResponseMessage response = await client.GetAsync(dummyUrl);               

            return response;
        }
        catch (Exception)
        {
            // log.
            throw;
        }
    }
}

अब यूनिट टेस्ट प्रोजेक्ट में मॉकिंग के लिए एक सहायक वर्ग बनाया गया है SendAsync। यहां यह एक ऐसा FakeHttpResponseHandlerवर्ग है inheriting DelegatingHandlerजो SendAsyncविधि को ओवरराइड करने का विकल्प प्रदान करेगा । अधिभावी के बाद SendAsyncसे प्रत्येक के लिए एक प्रतिक्रिया सेटअप करने के लिए विधि की जरूरत HTTP Requestहै जो बुला रहा है SendAsyncविधि, कि एक बनाने के लिए Dictionaryसाथ keyके रूप में Uriऔर valueके रूप में HttpResponseMessageतो यह है कि जब भी एक है HTTP Requestऔर यदि Uriमैचों SendAsyncवापस आ जाएगी कॉन्फ़िगर किया गया HttpResponseMessage

public class FakeHttpResponseHandler : DelegatingHandler
{
    private readonly IDictionary<Uri, HttpResponseMessage> fakeServiceResponse;
    private readonly JavaScriptSerializer javaScriptSerializer;
    public FakeHttpResponseHandler()
    {
        fakeServiceResponse =  new Dictionary<Uri, HttpResponseMessage>();
        javaScriptSerializer =  new JavaScriptSerializer();
    }

    /// <summary>
    /// Used for adding fake httpResponseMessage for the httpClient operation.
    /// </summary>
    /// <typeparam name="TQueryStringParameter"> query string parameter </typeparam>
    /// <param name="uri">Service end point URL.</param>
    /// <param name="httpResponseMessage"> Response expected when the service called.</param>
    public void AddFakeServiceResponse(Uri uri, HttpResponseMessage httpResponseMessage)
    {
        fakeServiceResponse.Remove(uri);
        fakeServiceResponse.Add(uri, httpResponseMessage);
    }

    /// <summary>
    /// Used for adding fake httpResponseMessage for the httpClient operation having query string parameter.
    /// </summary>
    /// <typeparam name="TQueryStringParameter"> query string parameter </typeparam>
    /// <param name="uri">Service end point URL.</param>
    /// <param name="httpResponseMessage"> Response expected when the service called.</param>
    /// <param name="requestParameter">Query string parameter.</param>
    public void AddFakeServiceResponse<TQueryStringParameter>(Uri uri, HttpResponseMessage httpResponseMessage, TQueryStringParameter requestParameter)
    {
        var serilizedQueryStringParameter = javaScriptSerializer.Serialize(requestParameter);
        var actualUri = new Uri(string.Concat(uri, serilizedQueryStringParameter));
        fakeServiceResponse.Remove(actualUri);
        fakeServiceResponse.Add(actualUri, httpResponseMessage);
    }

    // all method in HttpClient call use SendAsync method internally so we are overriding that method here.
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if(fakeServiceResponse.ContainsKey(request.RequestUri))
        {
            return Task.FromResult(fakeServiceResponse[request.RequestUri]);
        }

        return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)
        {
            RequestMessage = request,
            Content = new StringContent("Not matching fake found")
        });
    }
}

IServiceHelperमॉकिंग फ्रेमवर्क द्वारा या नीचे की तरह एक नया कार्यान्वयन बनाएं । यह FakeServiceHelperवर्ग हम कक्षा को इंजेक्ट करने के लिए उपयोग कर सकते हैं FakeHttpResponseHandlerताकि जब भी इसे HttpClientबनाया जाए तो classयह FakeHttpResponseHandler classवास्तविक कार्यान्वयन के बजाय इसका उपयोग करेगा ।

public class FakeServiceHelper : IServiceHelper
{
    private readonly DelegatingHandler delegatingHandler;

    public FakeServiceHelper(DelegatingHandler delegatingHandler)
    {
        this.delegatingHandler = delegatingHandler;
    }

    public HttpClient GetClient()
    {
        return new HttpClient(delegatingHandler);
    }
}

और परीक्षण FakeHttpResponseHandler classमें Uriऔर अपेक्षित जोड़कर कॉन्फ़िगर करें HttpResponseMessageUriवास्तविक होना चाहिए serviceendpoint Uriइतनी है कि जब overridden SendAsyncविधि वास्तविक से कहा जाता है serviceकार्यान्वयन यह से मेल खाएगी Uriमें Dictionaryऔर कॉन्फ़िगर के साथ जवाब HttpResponseMessageFakeHttpResponseHandler objectनकली IServiceHelperकार्यान्वयन के लिए इंजेक्शन को कॉन्फ़िगर करने के बाद । फिर FakeServiceHelper classवास्तविक सेवा पर इंजेक्ट करें जो override SendAsyncविधि का उपयोग करने के लिए वास्तविक सेवा बना देगा ।

[TestClass]
public class SampleServiceTest
{
    private FakeHttpResponseHandler fakeHttpResponseHandler;

    [TestInitialize]
    public void Initialize()
    {
        fakeHttpResponseHandler = new FakeHttpResponseHandler();
    }

    [TestMethod]
    public async Task GetMethodShouldReturnFakeResponse()
    {
        Uri uri = new Uri("http://www.dummyurl.com/api/controller/");
        const int dummyParam = 123456;
        const string expectdBody = "Expected Response";

        var expectedHttpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(expectdBody)
        };

        fakeHttpResponseHandler.AddFakeServiceResponse(uri, expectedHttpResponseMessage, dummyParam);

        var fakeServiceHelper = new FakeServiceHelper(fakeHttpResponseHandler);

        var sut = new SampleService(fakeServiceHelper);

        var response = await sut.Get(dummyParam);

        var responseBody = await response.Content.ReadAsStringAsync();

        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
        Assert.AreEqual(expectdBody, responseBody);
    }
}

गिटहब लिंक: जिसका नमूना कार्यान्वयन है


हालांकि यह कोड प्रश्न को हल कर सकता है, जिसमें यह भी बताया गया है कि यह समस्या कैसे और क्यों हल करती है, इससे वास्तव में आपके पोस्ट की गुणवत्ता को बेहतर बनाने में मदद मिलेगी, और शायद अधिक वोट भी मिलेंगे। याद रखें कि आप भविष्य में पाठकों के लिए प्रश्न का उत्तर दे रहे हैं, न कि केवल उस व्यक्ति से जो अब पूछ रहा है। कृपया स्पष्टीकरण जोड़ने के लिए अपने उत्तर को संपादित करें और संकेत दें कि क्या सीमाएँ और मान्यताएँ लागू होती हैं।
Богдан Опир

प्रतिक्रिया अद्यतन स्पष्टीकरण के लिए धन्यवाद @ БогданОпир।
घोष-अरुण

1

आप रिचर्डस्लैय मॉकहटप लाइब्रेरी का उपयोग कर सकते हैं जो HttpMessageHandler को मॉक करती है और परीक्षणों के दौरान उपयोग की जाने वाली HttpClient ऑब्जेक्ट को वापस कर सकती है।

गिटहब मॉकहटप

PM> इंस्टाल-पैकेज रिचर्डस्लेय। मॉकहटप

GitHub प्रलेखन से

MockHttp एक प्रतिस्थापन HttpMessageHandler को परिभाषित करता है, जो इंजन HttpClient को चलाता है, जो एक धाराप्रवाह विन्यास एपीआई प्रदान करता है और एक डिब्बाबंद प्रतिक्रिया प्रदान करता है। कॉल करने वाला (जैसे। आपके एप्लिकेशन की सेवा परत) इसकी उपस्थिति से अनजान है।

GitHub से उदाहरण

 var mockHttp = new MockHttpMessageHandler();

// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localhost/api/user/*")
        .Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON

// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();

var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;

var json = await response.Content.ReadAsStringAsync();

// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}

1

यह एक पुराना प्रश्न है, लेकिन मुझे लगता है कि मैं यहां नहीं देखा एक समाधान के साथ जवाब देने के लिए आग्रह करता हूं।
आप Microsoft को असेंबल (System.Net.Http) पर नकली कर सकते हैं और फिर परीक्षण के दौरान ShinsContext का उपयोग कर सकते हैं।

  1. VS 2017 में, System.Net.Http असेंबली पर राइट क्लिक करें और "Add Fakes विधानसभा" चुनें
  2. एक ShimsContext.Create () का उपयोग करके इकाई परीक्षण विधि में अपना कोड डालें। इस तरह, आप उस कोड को अलग कर सकते हैं जहाँ आप HttpClient को नकली बनाने की योजना बना रहे हैं।
  3. आपके कार्यान्वयन और परीक्षण पर निर्भर करता है, मैं सभी वांछित अभिनय को लागू करने का सुझाव दूंगा जहां आप HttpClient पर एक विधि कहते हैं और लौटे मूल्य को नकली करना चाहते हैं। ShimHttpClient.llInstances का उपयोग आपके परीक्षण के दौरान बनाए गए सभी उदाहरणों में आपके कार्यान्वयन को नकली करेगा। उदाहरण के लिए, यदि आप GetAsync () विधि को नकली बनाना चाहते हैं, तो निम्न कार्य करें:

    [TestMethod]
    public void FakeHttpClient()
    {
        using (ShimsContext.Create())
        {
            System.Net.Http.Fakes.ShimHttpClient.AllInstances.GetAsyncString = (c, requestUri) =>
            {
              //Return a service unavailable response
              var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
              var task = Task.FromResult(httpResponseMessage);
              return task;
            };
    
            //your implementation will use the fake method(s) automatically
            var client = new Connection(_httpClient);
            client.doSomething(); 
        }
    }

1

मैंने कुछ बहुत सरल किया, जैसा कि मैं एक डीआई वातावरण में था।

public class HttpHelper : IHttpHelper
{
    private ILogHelper _logHelper;

    public HttpHelper(ILogHelper logHelper)
    {
        _logHelper = logHelper;
    }

    public virtual async Task<HttpResponseMessage> GetAsync(string uri, Dictionary<string, string> headers = null)
    {
        HttpResponseMessage response;
        using (var client = new HttpClient())
        {
            if (headers != null)
            {
                foreach (var h in headers)
                {
                    client.DefaultRequestHeaders.Add(h.Key, h.Value);
                }
            }
            response = await client.GetAsync(uri);
        }

        return response;
    }

    public async Task<T> GetAsync<T>(string uri, Dictionary<string, string> headers = null)
    {
        ...

        rawResponse = await GetAsync(uri, headers);

        ...
    }

}

और नकली है:

    [TestInitialize]
    public void Initialize()
    {
       ...
        _httpHelper = new Mock<HttpHelper>(_logHelper.Object) { CallBase = true };
       ...
    }

    [TestMethod]
    public async Task SuccessStatusCode_WithAuthHeader()
    {
        ...

        _httpHelper.Setup(m => m.GetAsync(_uri, myHeaders)).Returns(
            Task<HttpResponseMessage>.Factory.StartNew(() =>
            {
                return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
                {
                    Content = new StringContent(JsonConvert.SerializeObject(_testData))
                };
            })
        );
        var result = await _httpHelper.Object.GetAsync<TestDTO>(...);

        Assert.AreEqual(...);
    }

1

आपको केवल HttpMessageHandlerकक्षा का एक परीक्षण संस्करण चाहिए, जिसे आप HttpClientctor पास करते हैं । मुख्य बिंदु यह है कि आपके परीक्षण HttpMessageHandlerवर्ग में एक HttpRequestHandlerप्रतिनिधि होगा जिसे कॉल करने वाले सेट कर सकते हैं और बस HttpRequestजिस तरह से चाहते हैं उसे संभाल सकते हैं।

public class FakeHttpMessageHandler : HttpMessageHandler
    {
        public Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> HttpRequestHandler { get; set; } =
        (r, c) => 
            new HttpResponseMessage
            {
                ReasonPhrase = r.RequestUri.AbsoluteUri,
                StatusCode = HttpStatusCode.OK
            };


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

आप इस वर्ग का एक उदाहरण का उपयोग करके एक ठोस HttpClient उदाहरण बना सकते हैं। HttpRequestHandler प्रतिनिधि के माध्यम से आप HttpClient से निवर्तमान http अनुरोधों पर पूर्ण नियंत्रण रखते हैं।


1

पॉइंटज़ेरोवॉ के उत्तर से प्रेरित होकर , यहाँ NUnit और FakeItEasy का उपयोग करके एक नमूना दिया गया है

SystemUnderTest इस उदाहरण में वह वर्ग है जिसे आप परीक्षण करना चाहते हैं - इसके लिए कोई नमूना सामग्री नहीं दी गई है, लेकिन मुझे लगता है कि आपके पास पहले से ही है!

[TestFixture]
public class HttpClientTests
{
    private ISystemUnderTest _systemUnderTest;
    private HttpMessageHandler _mockMessageHandler;

    [SetUp]
    public void Setup()
    {
        _mockMessageHandler = A.Fake<HttpMessageHandler>();
        var httpClient = new HttpClient(_mockMessageHandler);

        _systemUnderTest = new SystemUnderTest(httpClient);
    }

    [Test]
    public void HttpError()
    {
        // Arrange
        A.CallTo(_mockMessageHandler)
            .Where(x => x.Method.Name == "SendAsync")
            .WithReturnType<Task<HttpResponseMessage>>()
            .Returns(Task.FromResult(new HttpResponseMessage
            {
                StatusCode = HttpStatusCode.InternalServerError,
                Content = new StringContent("abcd")
            }));

        // Act
        var result = _systemUnderTest.DoSomething();

        // Assert
        // Assert.AreEqual(...);
    }
}

क्या होगा अगर मैं "x.Method.Name" के खिलाफ उल्लिखित विधि को एक पैरामीटर पास करना चाहता हूं ..?
शैलेश

0

शायद आपके वर्तमान प्रोजेक्ट में बदलाव करने के लिए कुछ कोड होंगे लेकिन नई परियोजनाओं के लिए आपको फ्लूरल का उपयोग करने पर बिल्कुल विचार करना चाहिए।

https://flurl.dev

यह एक धाराप्रवाह इंटरफ़ेस के साथ .NET के लिए एक HTTP क्लाइंट लाइब्रेरी है जो विशेष रूप से कोड के लिए परीक्षण क्षमता को सक्षम करता है जो इसका उपयोग HTTP अनुरोध करने के लिए करता है।

वेबसाइट पर कोड के बहुत सारे नमूने हैं लेकिन संक्षेप में आप इसे अपने कोड में इस तरह उपयोग करते हैं।

Usings जोड़ें।

using Flurl;
using Flurl.Http;

एक अनुरोध प्राप्त करें और प्रतिक्रिया पढ़ें।

public async Task SendGetRequest()
{
   var response = await "https://example.com".GetAsync();
   // ...
}

यूनिट परीक्षणों में फ्लूरल एक मॉक के रूप में कार्य करता है जिसे वांछित व्यवहार करने के लिए कॉन्फ़िगर किया जा सकता है और जो कॉल किए गए थे, उन्हें सत्यापित करने के लिए भी।

using (var httpTest = new HttpTest())
{
   // Arrange
   httpTest.RespondWith("OK", 200);

   // Act
   await sut.SendGetRequest();

   // Assert
   httpTest.ShouldHaveCalled("https://example.com")
      .WithVerb(HttpMethod.Get);
}

0

ध्यान से खोज करने के बाद, मैंने इसे पूरा करने के लिए सबसे अच्छा तरीका निकाला।

    private HttpResponseMessage response;

    [SetUp]
    public void Setup()
    {
        var handlerMock = new Mock<HttpMessageHandler>();

        handlerMock
           .Protected()
           .Setup<Task<HttpResponseMessage>>(
              "SendAsync",
              ItExpr.IsAny<HttpRequestMessage>(),
              ItExpr.IsAny<CancellationToken>())
           // This line will let you to change the response in each test method
           .ReturnsAsync(() => response);

        _httpClient = new HttpClient(handlerMock.Object);

        yourClinet = new YourClient( _httpClient);
    }

जैसा कि आपने देखा कि मैंने Moq और Moq.Protected पैकेज का उपयोग किया है।


0

मेरे 2 सेंट जोड़ने के लिए। विशिष्ट HTTP अनुरोध विधियों को या तो प्राप्त करने के लिए या पोस्ट करने के लिए। इसने मेरे लिए काम किया।

mockHttpMessageHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(a => a.Method == HttpMethod.Get), ItExpr.IsAny<CancellationToken>())
                                                .Returns(Task.FromResult(new HttpResponseMessage()
                                                {
                                                    StatusCode = HttpStatusCode.OK,
                                                    Content = new StringContent(""),
                                                })).Verifiable();
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.