단위 테스트에서 HttpClient 모의


111

단위 테스트에 사용할 코드를 래핑하는 데 몇 가지 문제가 있습니다. 문제는 이것입니다. IHttpHandler 인터페이스가 있습니다.

public interface IHttpHandler
{
    HttpClient client { get; }
}

그리고 그것을 사용하는 클래스, HttpHandler :

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

그런 다음 클라이언트 구현을 삽입하기 위해 simpleIOC를 사용하는 Connection 클래스 :

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)를 검색하는 Connection 클래스에 메서드가 있습니다. 그러나 저는이 클래스에 대한 단위 테스트를 작성하고 싶습니다. 분명히 실제 백엔드에 대한 테스트를 작성하고 싶지 않습니다. 나는 큰 성공없이 이것에 대한 좋은 대답을 구글에 시도했다. 나는 Moq를 사용하여 이전에 조롱했지만 httpClient와 같은 것은 결코 사용하지 않았습니다. 이 문제에 어떻게 접근해야합니까?

미리 감사드립니다.


1
HttpClient인터페이스에서를 노출하는 것이 문제입니다. 클라이언트가 HttpClient구체적인 클래스 를 사용하도록 강요하고 있습니다 . 대신, 당신은 노출해야 추상화 의를 HttpClient.
Mike Eason

좀 더 자세히 설명해 주시겠습니까? Connection 클래스를 사용하는 다른 클래스에서 HttpClient의 종속성을 원하지 않기 때문에 연결 클래스 생성자를 어떻게 빌드해야합니까? 예를 들어 Connection을 사용하는 다른 모든 클래스가 HttpClient에 종속되도록 만들기 때문에 Connection 생성자에 concerete HttpClient를 전달하고 싶지 않습니다.
tjugg

관심이 없어서 무엇을 검색 했습니까? 분명히 mockhttp는 SEO 개선 사항을 사용할 수 있습니다.
Richard Szalay 2016-04-05

@Mike-내 대답에서 언급했듯이 HttpClient를 추상화 할 필요가 없습니다. 있는 그대로 완벽하게 테스트 할 수 있습니다. 이 방법을 사용하는 백엔드없는 테스트 스위트가있는 수많은 프로젝트가 있습니다.
Richard Szalay 2016-04-05

답변:


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( Nkosi의 예제 참조 , 저보다 훨씬 나은 접근 방식입니다). HttpClient조롱 할 수있는 인터페이스가 있다면 인생은 훨씬 더 쉬울 것 입니다. 불행히도 그렇지 않습니다.

그러나이 예는 골든 티켓 이 아닙니다 . IHttpHandler여전히 네임 스페이스에 HttpResponseMessage속하는 에 의존 System.Net.Http하므로, 이외의 다른 구현이 필요한 경우 HttpClient응답을 HttpResponseMessage객체 로 변환하기 위해 일종의 매핑을 수행해야 합니다. 이것은 물론 여러 구현을 사용해야하는 경우 에만 문제 가 IHttpHandler되지만 그렇게하는 것처럼 보이지는 않으므로 세상의 끝은 아니지만 생각해 볼 사항입니다.

어쨌든, 추상화 된 IHttpHandler구체적인 HttpClient클래스 에 대해 걱정할 필요없이 간단히 조롱 할 수 있습니다.

비동기 메서드를 여전히 호출하지만 비동기 메서드 단위 테스트에 대해 걱정할 필요가 없으므로 비동기 메서드를 테스트하는 것이 좋습니다. 여기를 참조 하세요.


이것은 실제로 내 질문에 대한 답변입니다. Nkosis 답변도 맞아서 어떤 답변을 받아 들여야할지 모르겠지만이 답변으로 갈 것입니다. 노력에 대한 여러분 모두 감사
tjugg

@tjugg 기꺼이 도와드립니다. 유용하다고 생각되면 답변에 자유롭게 투표하십시오.
Nkosi

3
이 답변과 Nkosi의 주요 차이점은 훨씬 더 얇은 추상화라는 점에 주목할 가치가 있습니다. 씬은 대한 아마 좋은 겸손 객체
벤 아 론슨

228

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를 messagehandler Httphandler 클래스로 전달합니까? 또는 자신의 프로젝트에서 어떻게 구현
했습니까?

2
좋은 대답과 처음에는 알지 못했던 것. HttpClient로 작업하는 것이 그렇게 나쁘지 않습니다.
Bealer

6
클라이언트 주입을 처리하고 싶지 않지만 여전히 쉬운 테스트 가능성을 원하는 사람들에게는 달성하기가 쉽지 않습니다. 그냥 교체 var client = new HttpClient()var client = ClientFactory()필드와 설치 internal static Func<HttpClient> ClientFactory = () => new HttpClient();및 테스트 수준에서이 필드를 다시 작성할 수 있습니다.
Chris Marisic

3
@ChrisMarisic 당신은 주입을 대체 할 서비스 위치의 형태를 제안하고 있습니다. 서비스 위치는 잘 알려진 안티 패턴이므로 imho 주입이 선호됩니다.
MarioDS 2017 년

2
@MarioDS와 상관없이 HttpClient 인스턴스 를 전혀 주입해서는 안됩니다 . 당신이 당신이 주입되어야에 대한 constructer 주입을 사용하여 죽은 세트의 경우 HttpClientFactory에서와 같이가 Func<HttpClient>. HttpClient를 종속성이 아닌 구현 세부 사항으로 간주하므로 위에서 설명한대로 정적을 사용합니다. 나는 내부를 조작하는 테스트로 완전히 괜찮습니다. 순수주의에 관심이 있다면 전체 서버를 세우고 라이브 코드 경로를 테스트 할 것입니다. 모든 종류의 모의를 사용한다는 것은 실제 행동이 아닌 행동의 근사치를 수용한다는 것을 의미합니다.
Chris Marisic

39

가장 좋은 방법은 HttpClient를 래핑하는 대신 HttpMessageHandler를 모의하는 것입니다. 이 대답은 여전히 ​​HttpClient를 주입하여 단일 항목이되거나 종속성 주입으로 관리 될 수 있다는 점에서 고유합니다.

"HttpClient는 한 번 인스턴스화되고 응용 프로그램 수명 내내 재사용되도록 고안되었습니다." ( 출처 ).

SendAsync가 보호되기 때문에 HttpMessageHandler를 조롱하는 것은 약간 까다로울 수 있습니다. 다음은 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()살인자였다. 이 예에 감사드립니다. 소스를 전혀 수정하지 않고 테스트를 작성할 수 있습니다.
tyrion 2017

1
참고로 Moq 4.8은 보호 된 구성원에 대한 강력한 유형의 조롱을 지원합니다 -github.com/Moq/moq4/wiki/Quickstart
Richard Szalay

2
멋지네요. 또한 MOQ는 ReturnsAsync 그래서 코드가 같을 것이다 지원.ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})
KORD

감사 @kord, 내가 추가 한 대답에
PointZeroTwo

3
일부 매개 변수를 사용하여 "SandAsync"가 호출되었는지 확인할 수있는 방법이 있습니까? ... Protected (). Verify (...)를 사용하려고했지만 비동기 메서드에서 작동하지 않는 것 같습니다.
Rroman

29

이것은 일반적인 질문이며 HttpClient를 조롱하는 기능을 원하고 있었지만 마침내 HttpClient를 조롱해서는 안된다는 것을 깨닫게 된 것 같습니다. 그렇게하는 것이 논리적으로 보이지만, 오픈 소스 라이브러리에서 볼 수있는 것들에 세뇌 당했다고 생각합니다.

우리는 종종 "클라이언트"를 보게되는데, 코드에서 모방하여 격리 된 상태에서 테스트 할 수 있으므로 동일한 원칙을 HttpClient에 자동으로 적용하려고합니다. HttpClient는 실제로 많은 일을합니다. 당신은 그것을 HttpMessageHandler의 관리자로 생각할 수 있으므로 그것을 조롱하고 싶지 않으며, 그것이 여전히 인터페이스가없는 이유 입니다. 단위 테스트 또는 서비스 설계에 대해 정말로 관심이있는 부분은 응답을 반환하는 HttpMessageHandler이며이를 조롱 할 수 있습니다 .

또한 HttpClient를 더 큰 거래로 취급하기 시작해야한다는 점도 지적 할 가치가 있습니다. 예 : 새 HttpClients를 최소한으로 유지하십시오. 재사용하면 재사용이 가능하도록 설계되었으며 그렇게 할 경우 자원을 덜 사용합니다. 당신이 그것을 더 큰 거래처럼 다루기 시작하면, 그것을 조롱하고 싶어하는 것이 훨씬 더 잘못 느껴질 것이고 이제 메시지 핸들러는 클라이언트가 아닌 당신이 주입하는 것이 될 것입니다.

즉, 클라이언트 대신 핸들러에 대한 종속성을 설계하십시오. 더 좋은 점은 HttpClient를 사용하는 추상 "서비스"로, 핸들러를 주입하고 대신 주입 가능한 종속성으로 사용할 수 있습니다. 그런 다음 테스트에서 핸들러를 가짜로 만들어 테스트 설정에 대한 응답을 제어 할 수 있습니다.

HttpClient를 래핑하는 것은 엄청난 시간 낭비입니다.

업데이트 : Joshua Dooms의 예를 참조하십시오. 바로 제가 추천하는 것입니다.


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. 이는 구체적 으로 해당 기능을 제공하는 서비스였습니다 . 추상화를 사용하는 것은 깔끔하고 (내 의견) SOLID 접근 방식이며 프레임 워크가 변경됨에 따라 다른 것을 위해 기본 클라이언트를 전환해야하는 경우 코드를보다 유지 관리 할 수 ​​있습니다.

다음은 구현 방법에 대한 스 니펫입니다.

/// <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. 방법이 .
MarioDS 2017 년

3
@MarioDS 요점은 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는 추상 클래스이므로 mockable입니다. 테스트 방법은 다음과 같습니다.

[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것이 실제로 내가 찾은 가장 깨끗한 솔루션입니다.
Dylan Nicholson

8

한 가지 대안은 요청 URL과 일치하는 패턴을 기반으로 미리 준비된 응답을 반환하는 스텁 HTTP 서버를 설정하는 것입니다. 즉, 모의가 아닌 실제 HTTP 요청을 테스트합니다. 역사적으로 이것은 상당한 개발 노력이 필요했고 단위 테스트를 위해 고려하기에는 너무 느리지 만 OSS 라이브러리 WireMock.net 은 사용하기 쉽고 많은 테스트로 실행할 수있을만큼 빠르므로 고려할 가치가 있습니다. 설정은 몇 줄의 코드입니다.

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

여기에서 테스트에서 wiremock 사용에 대한 자세한 내용과 지침을 찾을 수 있습니다 .


8

여기 저에게 잘 맞는 간단한 솔루션이 있습니다.

moq mocking 라이브러리 사용.

// 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/


나는 또한 이것을 성공적으로 사용했습니다. 나는 이것이 아직 더 이상적인 의존성에 dfragging하는 것보다 선호하며 실제로 후드 아래에서 일어나는 일에 대해 조금 더 배우게됩니다. 좋은 점은 대부분의 방법이 SendAsync어쨌든 사용 되어 추가 설정이 필요하지 않다는 것입니다.
Steve Pettifer 19:39:19

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 컨테이너에 등록해야합니다. Autofac을 사용하는 경우 다음과 같습니다.

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우리가 mock을 사용할 때 각 테스트에 대해 각 동작을 설정할 수 있도록 델리게이트를 받아들이는 "mock"을 만들어 봅시다 .

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 (및 FluentAssertions, 단위 테스트를 더 읽기 쉽게 만드는 라이브러리)의 도움으로 PostAsync를 사용하는 메서드를 단위 테스트하는 데 필요한 모든 것이 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

파티에 조금 늦게 참여하지만 wiremocking을 사용하는 것을 좋아합니다 ( https://github.com/WireMock-Net/WireMock.Net 다운 스트림 REST 종속성이있는 dotnet 코어 마이크로 서비스의 통합 에서 가능할 때마다 )을 합니다.

IHttpClientFactory를 확장하는 TestHttpClientFactory를 구현하여 메서드를 재정의 할 수 있습니다.

HttpClient CreateClient (문자열 이름)

따라서 앱 내에서 명명 된 클라이언트를 사용할 때 wiremock에 연결된 HttpClient를 반환하는 것을 제어 할 수 있습니다.

이 접근 방식의 좋은 점은 테스트중인 애플리케이션 내에서 아무것도 변경하지 않고 서비스에 대한 실제 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);

2

때문에 HttpClient사용 SendAsync방법을 모두 수행하려면 HTTP Requests, 당신은 할 수있는 override SendAsync방법과 조롱HttpClient .

랩을 작성 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하는 각각에 대한 응답을 설정해야합니다. 이를 위해 Dictionarywith keyas Urivalueas 를 생성하여 a HttpResponseMessage가있을 때마다 일치 하는 HTTP Request경우 구성된 .UriSendAsyncHttpResponseMessage

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및 예상 HttpResponseMessage. 은 Uri실제해야 service엔드 포인트 Uri때 있도록 overridden SendAsync방법은 실제에서 호출 service구현는 일치 UriDictionary구성된와 응답을 HttpResponseMessage. 구성 후 FakeHttpResponseHandler 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);
    }
}

GitHub Link : 샘플 구현이 있습니다.


이 코드가 문제를 해결할 수 있지만 문제를 해결하는 방법과 이유에 대한 설명포함 하여 게시물의 품질을 향상시키는 데 실제로 도움이되며 아마도 더 많은 찬성 투표를 받게됩니다. 지금 질문하는 사람뿐만 아니라 미래에 독자를 위해 질문에 답하고 있다는 것을 기억하십시오. 제발 편집 설명을 추가하고 제한 및 가정이 적용 무엇의 표시를 제공하는 답변을.
Богдан Опир

피드백 업데이트 설명에 대해 @ БогданОпир 감사합니다.
ghosh-arun

1

RichardSzalay MockHttp를 사용할 수 있습니다 .HttpMessageHandler를 모방하고 테스트 중에 사용할 HttpClient 개체를 반환 할 수있는 라이브러리를 .

GitHub MockHttp

PM> 설치 패키지 RichardSzalay.MockHttp

GitHub 문서에서

MockHttp는 유창한 구성 API를 제공하고 미리 준비된 응답을 제공하는 HttpClient를 구동하는 엔진 인 대체 HttpMessageHandler를 정의합니다. 호출자 (예 : 애플리케이션의 서비스 계층)는 그 ​​존재를 계속 인식하지 못합니다.

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 어셈블리를 마우스 오른쪽 단추로 클릭하고 "가짜 어셈블리 추가"를 선택합니다.
  2. ShimsContext.Create () 아래의 단위 테스트 메서드에 코드를 넣으십시오. 이렇게하면 HttpClient를 가짜로 만들려는 코드를 격리 할 수 ​​있습니다.
  3. 구현 및 테스트에 따라 HttpClient에서 메서드를 호출하고 반환 된 값을 위조하려는 모든 원하는 동작을 구현하는 것이 좋습니다. ShimHttpClient.AllInstances를 사용하면 테스트 중에 생성 된 모든 인스턴스에서 구현이 위조됩니다. 예를 들어 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

DI 환경 에서처럼 아주 간단한 일을했습니다.

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

필요한 것은 ctor HttpMessageHandler에게 전달하는 클래스 의 테스트 버전입니다 HttpClient. 요점은 테스트 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

PointZeroTwo의 답변 에서 영감을 얻은 다음은 NUnitFakeItEasy를 사용하는 샘플 입니다.

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"..에 대해 언급 된 매개 변수를 메소드에 전달하려면 어떻게해야합니까?
Shailesh

0

현재 프로젝트에서 변경해야 할 코드가있을 수 있지만 새 프로젝트의 경우 Flurl 사용을 절대적으로 고려해야합니다.

https://flurl.dev

이는 HTTP 요청을 만드는 데 사용하는 코드에 대한 테스트 가능성을 특별히 활성화하는 유창한 인터페이스가있는 .NET 용 HTTP 클라이언트 라이브러리입니다.

웹 사이트에는 많은 코드 샘플이 있지만 간단히 말해서 코드에서 이와 같이 사용합니다.

사용을 추가하십시오.

using Flurl;
using Flurl.Http;

get 요청을 보내고 응답을 읽으십시오.

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

단위 테스트에서 Flurl은 원하는대로 동작하도록 구성 할 수 있고 수행 된 호출을 확인하기 위해 구성 할 수있는 모의 역할을합니다.

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 요청 메소드를 모의하려면 Get 또는 Post 중 하나입니다. 이것은 나를 위해 일했습니다.

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();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.