如何使用 NSubstitute 模拟 HttpClient

问题描述 投票:0回答:1

我正在尝试使用 NSubstitute 来模拟 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() 被 必填。

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

诊断信息:

剩余(非绑定)参数规范: 任何 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 
            }
c# unit-testing httpclient virtual nsubstitute
1个回答
22
投票

首先我们需要创建

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<T>
而不是
Substitute.For<T>

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

最后,我们现在可以使用 NSubstitute 拦截处理程序上对

Send()
的调用,该调用由
HttpClient
对于每个请求进行调用,并通过客户端返回我们模拟的
HttpResponseMessage

var mockResponse = new HttpResponseMessage(HttpStatusCode.OK);

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

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

.NET 6

由于 .NET 6 向

protected virtual Send()
类引入了
HttpMessageHandler
方法(如果您使用同步
HttpClient
调用,也需要重写该方法),因此需要对我们的
MockHttpMessageHandler
:

进行一些修改
public class MockHttpMessageHandler : HttpMessageHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(MockSend(request, cancellationToken));
    }

    protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return MockSend(request, cancellationToken);
    }

    public virtual HttpResponseMessage MockSend(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.