Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I mock/stub HttpRequestMessage in .NET Core?

I have a function that I'm porting from Azure Functions v1 to v2, and as part of that I've run into an issue updating my unit tests which create a stubbed HttpRequestMessage. Here's the code that works on Functions v1, targeting .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");
        }
    }
}

And my test code

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\""));
    }
}

After porting to v2, my function project csproj file looks like this. The only differences are that I'm no longer targeting full framework, and the addition of the AzureFunctionsVersion node.

<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>

And this is the csproj for my test project after retargeting to .NET Core 2.0

<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>

Previously, I was using the answers to this question to properly stub out HttpRequestMessage but that no longer appears to work.

When I try to compile this, I get the following compilation errors

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

So if I just remove the line, hoping that the fix is no longer needed

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

Instead I get this error message

System.InvalidOperationException : The HttpRequestMessage instance is not properly initialized. Use HttpRequestMessageHttpContextExtensions.GetHttpRequestMessage to create an HttpRequestMessage for the current request.

Trying to follow the error message's directions didn't prove fruitful to me, I tried to set the HttpContext like was done in this question

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

But that gave me a different error (same as in the question)

System.ArgumentNullException : Value cannot be null.

Parameter name: provider

like image 245
Kevin Secrist Avatar asked Apr 01 '19 21:04

Kevin Secrist


1 Answers

Azure Functions is somewhat based on ASP.NET MVC WebApi, which has had some changes for .NET Core. HttpConfiguration for example doesn't appear to be available in any packages that target .NET Core/Standard. In order to fix this, I had to install a couple of packages in my test project, namely Microsoft.AspNetCore.Mvc for AddMvc() and Microsoft.AspNetCore.Mvc.WebApiCompatShim for .AddWebApiConventions(), which:

Provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations

So I added these to my test project

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

and now my test project looks like this

<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>

To mock the services that the ArgumentNullException was implying were missing (which in this case I think are MediaTypeFormatters), I had to essentially bootstrap MVC to get the HttpContext initialized properly.

[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\""));
}

And that makes the test compile, run, and pass.

like image 89
Kevin Secrist Avatar answered Sep 18 '22 12:09

Kevin Secrist