【问题标题】:How to Mock HttpClient using Substitute如何使用 Substitute 模拟 HttpClient
【发布时间】:2021-08-07 02:01:34
【问题描述】:

我正在尝试使用 Substitute 来模拟 HttpClient。代码如下:

public static HttpClient GetHttpClient(bool isSucess = true, string methodType = "GET")
        {
            var mockIHttpMessageHandler = Substitute.For<IMockHttpMessageHandler>();
            var mockHttpMessageHandler = Substitute.For<MockHttpMessageHandler>(mockIHttpMessageHandler);
            var httpResponse = Substitute.For<HttpResponseMessage>();
            httpResponse.Content = new StringContent("\"test\"");
            if (isSucess)
                httpResponse.StatusCode = HttpStatusCode.OK;
            else
                httpResponse.StatusCode = HttpStatusCode.NotFound;

            var mockHttpClient = Substitute.For<HttpClient>(mockHttpMessageHandler);
            mockHttpClient.BaseAddress = new Uri("http://localhost");

            if(methodType != "POST"){
                mockHttpClient.GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);
            }
            return mockHttpClient;
        }

但是,我在这一行遇到了错误:

mockHttpClient.GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);

错误是

NSubstitute.Exceptions.RedundantArgumentMatcherException: '一些 参数说明(例如 Arg.Is、Arg.Any)在 最后一次通话。

这通常是由使用参数规范和对成员的调用引起的 NSubstitute 不处理(例如非虚拟成员或调用 一个不是替代品的实例),或用于其他目的 指定调用(例如使用 arg 规范作为返回值)。为了 示例:

var sub = Substitute.For<SomeClass>();
var realType = new MyRealType(sub);
// INCORRECT, arg spec used on realType, not a substitute:
realType.SomeMethod(Arg.Any<int>()).Returns(2);
// INCORRECT, arg spec used as a return value, not to specify a call:
sub.VirtualMethod(2).Returns(Arg.Any<int>());
// INCORRECT, arg spec used with a non-virtual method:
sub.NonVirtualMethod(Arg.Any<int>()).Returns(2);
// CORRECT, arg spec used to specify virtual call on a substitute:
sub.VirtualMethod(Arg.Any<int>()).Returns(2);

要解决此问题,请确保您仅在调用中使用参数规范 给替补。如果您的替代品是一个班级,请确保该成员是 虚拟的。

另一个可能的原因是参数规范类型不匹配 实际的参数类型,但代码由于隐式转换而编译。 例如,使用了 Arg.Any(),但使用了 Arg.Any() 必填。

注意:此异常的原因可能是在先前执行的 测试。使用下面的诊断来查看任何冗余 arg 的类型 规范,然后找出它们的创建位置。

诊断信息:

剩余(非绑定)参数规范: 任何 Uri

所有参数说明: 任何 Uri

他们是否建议我需要更改 getAsync 方法? GetAsync没有虚方法

编辑:

我也尝试过如下删除 HttpClient 的 NSubstitute,但还是出现同样的错误:

public static HttpClient GetHttpClient(bool isSucess = true, string methodType = "GET")
            {
                var mockIHttpMessageHandler = Substitute.For<IMockHttpMessageHandler>();
                var mockHttpMessageHandler = Substitute.For<MockHttpMessageHandler>(mockIHttpMessageHandler);
                var httpResponse = Substitute.For<HttpResponseMessage>();
                httpResponse.Content = new StringContent("\"test\"");
                if (isSucess)
                    httpResponse.StatusCode = HttpStatusCode.OK;
                else
                    httpResponse.StatusCode = HttpStatusCode.NotFound;
    
                 var httpClient = new HttpClient(mockHttpMessageHandler);
                httpClient = new Uri("http://localhost");
    
                if(methodType != "POST"){
                    httpClient .GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);
                }
                return httpClient 
            }

【问题讨论】:

  • 所有调用 GET POST 等调用Send 方法。但是,我建议不要嘲笑 httpclient。使用实际的httpclient。模拟处理程序(客户端调用)并让它处理请求。
  • @Nkosi 谢谢,我在没有使用 NSubstitute 的情况下尝试过,但我得到了同样的错误
  • 尝试将NSubstitute.Analyzers 包添加到您的测试项目中。它也许能够找出造成这种情况的原因。

标签: c# unit-testing httpclient virtual nsubstitute


【解决方案1】:

我很欣赏这是一个旧的(ish)问题,但它今天在“使用 nsubstitute 的 c# mock httpclient”的 Google 搜索结果的顶部,所以我认为答案会很有用。

首先我们需要创建一个HttpMessageHandler 的模拟实现。如您所见,我们正在覆盖受保护的SendAsync() 方法并通过我们的公共Send() 方法公开它的主体。

public class MockHttpMessageHandler : HttpMessageHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Send(request, cancellationToken);
    }

    public virtual Task<HttpResponseMessage> Send(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}

接下来我们需要设置我们的模拟。请注意,我使用的是Substitute.ForPartsOf&lt;T&gt; 而不是Substitute.For&lt;T&gt;

var mockHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
var httpClient = new HttpClient(mockHttpMessageHandler);

最后,我们现在可以使用 NSubstitute 拦截处理程序上对 Send() 的调用,该处理程序由 HttpClient 为每个请求调用,并通过客户端返回我们模拟的 HttpResponseMessage

var mockResponse = new HttpResponseMessage(HttpStatusCode.OK);

mockHttpMessageHandler.Send(Arg.Any<AnyHttpRequestMessage>(), Arg.Any<CancellationToken>())
   .Returns(mockResponse);

var result = await httpClient.GetAsync<string>("https://tempuri.org");

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-10
    • 1970-01-01
    • 1970-01-01
    • 2013-12-30
    • 1970-01-01
    • 1970-01-01
    • 2018-10-02
    • 1970-01-01
    相关资源
    最近更新 更多