如何在 .NET Core 中模拟/存根 HttpRequestMessage?

Posted

技术标签:

【中文标题】如何在 .NET Core 中模拟/存根 HttpRequestMessage?【英文标题】:How do I mock/stub HttpRequestMessage in .NET Core? 【发布时间】:2019-08-23 03:17:32 【问题描述】:

我有一个要从 Azure Functions v1 移植到 v2 的函数,作为其中的一部分,我在更新创建存根 HttpRequestMessage 的单元测试时遇到了问题。这是适用于 Functions v1 的代码,针对 .NET Framework 4.7

public class FunctionExample

    [FunctionName(nameof(SomeFunction))]
    public static async Task<HttpResponseMessage> SomeFunction
    (
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "SomeFunction")]
        HttpRequestMessage request
    )
    
        var body = await request.Content.ReadAsStringAsync();
        if (string.IsNullOrEmpty(body))
        
            return request.CreateResponse(HttpStatusCode.BadRequest, "Bad job");
        
        else
        
            return request.CreateResponse(HttpStatusCode.OK, "Good job");
        
    

还有我的测试代码

public class FunctionExampleTests

    [Test]
    public async Task TestSomeFunction()
    
        var request = new HttpRequestMessage
        
            Method = HttpMethod.Post,
            RequestUri = new Uri("http://localhost/"),
            Content = new StringContent("", Encoding.UTF8, "application/json")
        ;

        request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey,
            new HttpConfiguration());

        var response = await FunctionExample.SomeFunction(request);

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

        Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
        Assert.That(content, Is.EqualTo("\"Bad job\""));
    

移植到v2后,我的函数项目csproj文件是这样的。唯一的区别是我不再以完整框架为目标,并且添加了AzureFunctionsVersion 节点。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <AzureFunctionsVersion>v2</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
  </ItemGroup>
</Project>

这是我重定向到 .NET Core 2.0 后测试项目的 csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="nunit" Version="3.11.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\FunctionExample\FunctionExample.csproj" />
  </ItemGroup>
</Project>

以前,我使用this question 的答案正确地存根 HttpRequestMessage,但这似乎不再起作用。

当我尝试编译它时,我得到以下编译错误

Error   CS0246  The type or namespace name 'HttpConfiguration' could not be found (are you missing a using directive or an assembly reference?)
Error   CS0103  The name 'HttpPropertyKeys' does not exist in the current context

所以如果我只是删除该行,希望不再需要修复

request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());

相反,我收到此错误消息

System.InvalidOperationException : HttpRequestMessage 实例未正确初始化。使用 HttpRequestMessageHttpContextExtensions.GetHttpRequestMessage 为当前请求创建一个 HttpRequestMessage。

尝试按照错误消息的指示对我来说没有结果,我尝试设置 HttpContext like was done in this question

request.Properties.Add(nameof(HttpContext), new DefaultHttpContext());

但这给了我一个不同的错误(与问题相同)

System.ArgumentNullException : 值不能为空。

参数名称:提供者

【问题讨论】:

【参考方案1】:

Azure Functions 在某种程度上基于 ASP.NET MVC WebApi,它对 .NET Core 进行了一些更改。例如,HttpConfiguration 在任何面向 .NET Core/Standard 的包中似乎都不可用。为了解决这个问题,我必须在我的 test 项目中安装几个包,即Microsoft.AspNetCore.Mvc 用于AddMvc() 和Microsoft.AspNetCore.Mvc.WebApiCompatShim 用于.AddWebApiConventions(),其中:

在 ASP.NET Core MVC 中提供与 ASP.NET Web API 2 的兼容性,以简化现有 Web API 实现的迁移

所以我将这些添加到我的测试项目中

<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.WebApiCompatShim" Version="2.2.0" />

现在我的测试项目是这样的

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="nunit" Version="3.11.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.WebApiCompatShim" Version="2.2.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\FunctionExampleFunction\FunctionExampleFunction.csproj" />
  </ItemGroup>
</Project>

为了模拟 ArgumentNullException 暗示的服务缺失(在这种情况下我认为是 MediaTypeFormatters),我必须从本质上引导 MVC 以正确初始化 HttpContext。

[Test]
public async Task TestSomeFunction()

    var request = new HttpRequestMessage
    
        Method = HttpMethod.Post,
        RequestUri = new Uri("http://localhost/"),
        Content = new StringContent("", Encoding.UTF8, "application/json")
    ;

    var services = new ServiceCollection()
        .AddMvc()
        .AddWebApiConventions()
        .Services
        .BuildServiceProvider();

    request.Properties.Add(nameof(HttpContext), new DefaultHttpContext
    
        RequestServices = services
    );

    var response = await FunctionExample.SomeFunction(request);

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

    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
    Assert.That(content, Is.EqualTo("\"Bad job\""));

这使得测试可以编译、运行和通过。

【讨论】:

将与较新版本相关的调查结果汇总在一起。干得好。 优秀的答案!每个服务集合应该在每个测试中初始化还是可以共享? 谢谢!如果您按照此处所述使用服务集合,您应该能够共享它。不过请记住在每个测试中使用新的 HttpRequestMessage。

以上是关于如何在 .NET Core 中模拟/存根 HttpRequestMessage?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Typhoon 为集成测试注入虚假、存根或模拟依赖项

扩展特征的单元测试类 - 我如何在特征中模拟和存根方法?

是否可以在 Xcode 7 自动化 UI 测试中存根 HTTP 请求?

PHPUnit 中的模拟与存根

如何使用 Moq 从 IHttpClientFactory 模拟 HTTPClient 并结合 .NET Core 中的 Polly 策略

如何在 Flutter 集成测试中最好地存根/模拟 rest API 调用