8

鉴于以下代码,我试图弄清楚如何将 FakeItEasy 与 HttpClient 一起使用:

public Foo(string key, HttpClient httpClient = null)
{ .. }

public void DoGet()
{
    ....

    if (_httpClient == null)
    {
        _httpClient = new HttpClient();
    }

    var response = _httpClient.GetAsync("user/1);
}

public void DoPost(foo Foo)
{
    if (_httpClient == null)
    {
        _httpClient = new HttpClient();
    }

    var formData = new Dictionary<string, string>
    {
        {"Name", "Joe smith"},
        {"Age", "40"}
    };    

    var response = _httpClient.PostAsync("user", 
        new FormUrlEncodedContent(formData));
}

所以我不知道如何使用FakeItEasy,来伪造 HttpClient 的GetAsyncPostAsync方法。

生产代码不会通过 HttpClient,但单元测试会通过 FakeItEasy 制作的假实例。

例如。

[Fact]
public void GivenBlah_DoGet_DoesSomething()
{
    // Arrange.
    var httpClient A.Fake<HttpClient>(); // <-- need help here.
    var foo = new Foo("aa", httpClient);

    // Act.
    foo.DoGet();

    // Assert....
}

更新:

我认为 FiE(和大多数模拟包)适用于接口或虚拟方法。所以对于这个问题,让我们假设 and 方法是虚拟的......GetAsyncPostAsync:)

4

5 回答 5

20

这是我的(或多或少)通用 FakeHttpMessageHandler。

public class FakeHttpMessageHandler : HttpMessageHandler
{
    private HttpResponseMessage _response;

    public static HttpMessageHandler GetHttpMessageHandler( string content, HttpStatusCode httpStatusCode )
    {
        var memStream = new MemoryStream();

        var sw = new StreamWriter( memStream );
        sw.Write( content );
        sw.Flush();
        memStream.Position = 0;

        var httpContent = new StreamContent( memStream );

        var response = new HttpResponseMessage()
        {
            StatusCode = httpStatusCode,
            Content = httpContent
        };

        var messageHandler = new FakeHttpMessageHandler( response );

        return messageHandler;
    }

    public FakeHttpMessageHandler( HttpResponseMessage response )
    {
        _response = response;
    }

    protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken )
    {
        var tcs = new TaskCompletionSource<HttpResponseMessage>();

        tcs.SetResult( _response );

        return tcs.Task;
    }
}

这是我的一个测试中使用它的一个例子,它期望一些 JSON 作为返回值。

const string json = "{\"success\": true}";

var messageHandler = FakeHttpMessageHandler.GetHttpMessageHandler( 
    json, HttpStatusCode.BadRequest );
var httpClient = new HttpClient( messageHandler );

您现在将注入httpClient您的测试类(使用您喜欢的任何注入机制),当GetAsync被调用时,您messageHandler将返回您告诉它的结果。

于 2014-03-07T16:17:10.503 回答
3

当我需要与 Gravatar 服务交互时,我做了类似的事情。我尝试使用假货/模拟,但发现使用 HttpClient 是不可能的。相反,我想出了一个自定义的 HttpMessageHandler 类,它可以让我预加载预期的响应,如下所示:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Tigra.Gravatar.LogFetcher.Specifications
    {
    /// <summary>
    ///   Class LoggingHttpMessageHandler.
    ///   Provides a fake HttpMessageHandler that can be injected into HttpClient.
    ///   The class requires a ready-made response message to be passed in the constructor,
    ///   which is simply returned when requested. Additionally, the web request is logged in the
    ///   RequestMessage property for later examination.
    /// </summary>
    public class LoggingHttpMessageHandler : DelegatingHandler
        {
        internal HttpResponseMessage ResponseMessage { get; private set; }
        internal HttpRequestMessage RequestMessage { get; private set; }

        public LoggingHttpMessageHandler(HttpResponseMessage responseMessage)
            {
            ResponseMessage = responseMessage;
            }

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

然后我的测试上下文设置是这样的:

public class with_fake_gravatar_web_service
    {
    Establish context = () =>
        {
        MessageHandler = new LoggingHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK));
        GravatarClient = new HttpClient(MessageHandler);
        Filesystem = A.Fake<FakeFileSystemWrapper>();
        Fetcher = new GravatarFetcher(Committers, GravatarClient, Filesystem);
        };

    protected static LoggingHttpMessageHandler MessageHandler;
    protected static HttpClient GravatarClient;
    protected static FakeFileSystemWrapper Filesystem;
    }

然后,这是一个使用它的测试(规范)示例:

[Subject(typeof(GravatarFetcher), "Web service")]
public class when_fetching_imagaes_from_gravatar_web_service : with_fake_gravatar_web_service
    {
    Because of = () =>
        {
        var result = Fetcher.FetchGravatars(@"c:\"); // This makes the web request
        Task.WaitAll(result.ToArray());
        //"http://www.gravatar.com/avatar/".md5_hex(lc $email)."?d=404&size=".$size; 
        UriPath = MessageHandler.RequestMessage.RequestUri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
        };

    It should_make_request_from_gravatar_dot_com =
        () => MessageHandler.RequestMessage.RequestUri.Host.ShouldEqual("www.gravatar.com");

    It should_make_a_get_request = () => MessageHandler.RequestMessage.Method.ShouldEqual(HttpMethod.Get);
    // see https://en.gravatar.com/site/check/tim@tigranetworks.co.uk
    It should_request_the_gravatar_hash_for_tim_long =
        () => UriPath.ShouldStartWith("avatar/df0478426c0e47cc5e557d5391e5255d");

    static string UriPath;
    }

您可以在http://stash.teamserver.tigranetworks.co.uk/users/timlong/repos/tigra.gravatar.logfetcher/browse看到完整的源代码

于 2014-03-07T01:26:21.390 回答
3

You could also create an AbstractHandler on which you can intercept a public abstract method. For instance:

public abstract class AbstractHandler : HttpClientHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(SendAsync(request.Method, request.RequestUri.AbsoluteUri));
    }

    public abstract HttpResponseMessage SendAsync(HttpMethod method, string url);
}

Then you can intercept calls to the AbstractHandler.SendAsync(HttpMethod method, string url) like:

// Arrange
var httpMessageHandler = A.Fake<AbstractHandler>(options => options.CallsBaseMethods());
A.CallTo(() => httpMessageHandler.SendAsync(A<HttpMethod>._, A<string>._)).Returns(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Result")});
var httpClient = new HttpClient(httpMessageHandler);

// Act
var result = await httpClient.GetAsync("https://google.com/");

// Assert
Assert.Equal("Result", await result.Content.ReadAsStringAsync());
A.CallTo(() => httpMessageHandler.SendAsync(A<HttpMethod>._, "https://google.com/")).MustHaveHappenedOnceExactly();

More information can be found on this blog: https://www.meziantou.net/mocking-an-httpclient-using-an-httpclienthandler.htm

于 2019-09-16T06:23:26.257 回答
2

FakeItEasy 与大多数模拟库一样,不会为非抽象组件创建代理。在 的情况下HttpClientGetAsyncandPostAsync方法既不是virtualabstract,所以你不能直接创建它们的存根实现。请参阅https://github.com/FakeItEasy/FakeItEasy/wiki/What-c​​an-be-faked

在这种情况下,您需要一个不同的抽象作为依赖项——一个HttpClient可以实现的抽象,但其他实现也可以,包括模拟/存根。

于 2014-03-07T01:03:44.870 回答
-1

This isn't answering your question directly, but I wrote a library a while back that provides an API for stubbing out requests/responses. It's pretty flexible, and supports ordered/unordered matching as well as a customisable fallback system for unmatched requests.

It's available on GitHub here: https://github.com/richardszalay/mockhttp

于 2015-11-18T22:25:48.153 回答