C# 如何在.NET测试中传入模拟的HttpClient?

C# 如何在.NET测试中传入模拟的HttpClient?,c#,.net,mocking,dotnet-httpclient,microsoft-fakes,C#,.net,Mocking,Dotnet Httpclient,Microsoft Fakes,我有一个服务,它使用Microsoft.Net.Http检索一些Json数据。太好了 当然,我不希望我的单元测试击中实际的服务器(否则,这是一个集成测试) 这是我的服务ctor(它使用依赖注入…) 我不知道我怎么能用。。。说最低起订量或FakeItEasy 我想确保当我的服务调用GetAsync或PostAsync时。。然后我可以假装那些电话 有什么建议吗 我希望我不需要自己做包装纸。。因为那是废话:(微软不可能在这方面疏忽,对吧 (是的,做包装纸很容易……我以前做过……但这就是重点!)您可以看

我有一个服务,它使用
Microsoft.Net.Http
检索一些
Json
数据。太好了

当然,我不希望我的单元测试击中实际的服务器(否则,这是一个集成测试)

这是我的服务ctor(它使用依赖注入…)

我不知道我怎么能用。。。说<代码>最低起订量或
FakeItEasy

我想确保当我的服务调用
GetAsync
PostAsync
时。。然后我可以假装那些电话

有什么建议吗

我希望我不需要自己做包装纸。。因为那是废话:(微软不可能在这方面疏忽,对吧


(是的,做包装纸很容易……我以前做过……但这就是重点!)

您可以看一看,尤其是
垫片部分。使用它们,您可以修改您的HttpClient本身的行为。前提是,您使用的是VS Premium或Ultimate。

您可以用一个假的HttpMessageHandler替换核心HttpMessageHandler。看起来像这样的东西

public class FakeResponseHandler : DelegatingHandler
{
    private readonly Dictionary<Uri, HttpResponseMessage> _FakeResponses = new Dictionary<Uri, HttpResponseMessage>();

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage)
    {
        _FakeResponses.Add(uri, responseMessage);
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        if (_FakeResponses.ContainsKey(request.RequestUri))
        {
            return Task.FromResult(_FakeResponses[request.RequestUri]);
        }
        else
        {
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request });
        }
    }
}

我只想对的答案做一个小小的更改,即使用Task.FromResult来避免关于异步方法需要等待运算符的警告

public class FakeResponseHandler : DelegatingHandler
{
    private readonly Dictionary<Uri, HttpResponseMessage> _FakeResponses = new Dictionary<Uri, HttpResponseMessage>();

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage)
    {
        _FakeResponses.Add(uri, responseMessage);
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        if (_FakeResponses.ContainsKey(request.RequestUri))
        {
            return Task.FromResult(_FakeResponses[request.RequestUri]);
        }
        else
        {
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request });
        }
    }
}
公共类FakerResponseHandler:DelegatingHandler
{
专用只读词典_FakeResponses=新词典();
public void AddFakeResponse(Uri、HttpResponseMessage和responseMessage)
{
_添加(uri,responseMessage);
}
受保护的覆盖任务SendAsync(HttpRequestMessage请求,System.Threading.CancellationToken CancellationToken)
{
if(_FakeResponses.ContainsKey(request.RequestUri))
{
返回Task.FromResult(_fakerResponses[request.RequestUri]);
}
其他的
{
返回Task.FromResult(新的HttpResponseMessage(HttpStatusCode.NotFound){RequestMessage=request});
}
}
}

我知道这是一个老问题,但我在搜索这个主题时遇到了这个问题,并找到了一个非常好的解决方案,使测试
HttpClient
更容易

可通过nuget获得:

下面是有关用法的快速查看:

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://localost/api/user/1234");
// or without await: var response = client.GetAsync("http://localost/api/user/1234").Result;

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

// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}
更多信息请参见github项目页面。
希望这会有用。

“我希望-我不需要自己做包装器”-您使用了多少
HttpClient
的方法和属性?通常可以为这些方法和属性创建一个接口,然后您可以对其进行模拟。(就像我在OP中提到的那样)同意-这很简单简单…但我想我不应该这么做?这应该是核心的东西…对吗?这是我唯一的选择吗?据我所知,如果这些调用是虚拟的,你可以存根这些调用。因为它们不是,我想你需要写一个包装器(在我看来,这是更干净的方式).在这种情况下,请参见,我通过“.net mock”找到的不是没有接口的虚拟方法”。被接受的答案建议使用,我所知道的唯一一个没有虚拟方法的.NET模拟框架。我无法访问这两个昂贵的版本。这是一个有趣问题的好解决方案。除了通过网络发送HTTP之外,它几乎做了所有事情。您的示例不会按原样编译。您需要使用Task.FromResult来返回n您的HttpResponseMessage实例。@Ananke SendAsync方法上的async关键字为您发挥了神奇的作用。是否有人认为这比使用
IHttpClient
接口来模拟要复杂得多?结果显示,尽管方法名为:我会小心使用该技术ique适用于任何对性能敏感的环境,因为它可能会导致线程切换而没有任何好处。如果您确实担心此警告,则只需删除async关键字并使用Task.FromResult。如果实现不是基于任务的,则最好返回Task.FromResult并删除async关键字,而不是使用Task.Run.return任务。FromResult(响应);
public class FakeResponseHandler : DelegatingHandler
{
    private readonly Dictionary<Uri, HttpResponseMessage> _FakeResponses = new Dictionary<Uri, HttpResponseMessage>();

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage)
    {
        _FakeResponses.Add(uri, responseMessage);
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        if (_FakeResponses.ContainsKey(request.RequestUri))
        {
            return Task.FromResult(_FakeResponses[request.RequestUri]);
        }
        else
        {
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request });
        }
    }
}
PM> Install-Package 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://localost/api/user/1234");
// or without await: var response = client.GetAsync("http://localost/api/user/1234").Result;

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

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