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




unit-testing moq (12)

प्वाइंटज़ेरोवॉ के उत्तर से प्रेरित होकर , यहाँ NUnit और FakeItEasy का उपयोग करके एक नमूना 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(...);
    }
}

मेरे पास कुछ मुद्दे हैं जो मेरे कोड को इकाई परीक्षणों में उपयोग करने के लिए लपेटने की कोशिश कर रहे हैं। मुद्दे यह हैं। 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 जैसी किसी चीज का इस्तेमाल नहीं करता। मुझे इस समस्या से कैसे संपर्क करना चाहिए?

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


आपको केवल HttpMessageHandler कक्षा का एक परीक्षण संस्करण चाहिए, जिसे आप HttpClient ctor पास करते हैं । मुख्य बिंदु यह है कि आपके परीक्षण 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 अनुरोधों पर पूर्ण नियंत्रण रखते हैं।


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

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

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

HttpCless की एक्स्टेंसिबिलिटी 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'}

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

https://github.com/richardszalay/mockhttp

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

https://github.com/richardszalay/mockhttp

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

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://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'}

आपका इंटरफ़ेस ठोस 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 अपना खुद का HttpClientHandler बनाता है, तो आप निश्चित रूप से कई कक्षाएं बना सकते हैं जो IHttpHandler को विभिन्न तरीकों से कार्यान्वित IHttpHandler हैं।

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

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

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

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


जैसा कि टिप्पणियों में भी उल्लेख किया गया है कि आपको 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 साथ दूर करने की सलाह IHttpHandler और HttpClient अमूर्त IHttpClient उपयोग करें। लेकिन मैं अभी नहीं उठा रहा हूँ क्योंकि आप सार क्लाइंट के सदस्यों के साथ अपने हैंडलर इंटरफेस के शरीर को बदल सकते हैं।

IHttpClient का कार्यान्वयन तब वास्तविक / ठोस HttpClient या उस मामले के लिए किसी भी अन्य ऑब्जेक्ट को IHttpClient / अनुकूलित करने के लिए उपयोग किया जा सकता है, जिसका उपयोग 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();  
}

पार्टी में थोड़ा देर से शामिल होना, लेकिन मैं जब भी संभव हो, डाउनस्ट्रीम REST निर्भरता वाले डॉटनेट कोर माइक्रोसर्विट के एकीकरण में संभव हो तो वायरमॉकिंग ( WireMock.net ) का उपयोग करना पसंद करता हूं।

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

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

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

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

    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);

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

अब तक HttpClient का मजाक HttpClient का सबसे आसान तरीका उस विशेष विधि का केवल मजाक 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 पुष्टि करने के लिए यहां https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs


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

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

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

// 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>()
);

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

[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(); 
    }
}

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

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(...);
    }

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

public interface IHttpClientFactory
{
    HttpClient Create();
}

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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





moq