1

I'm porting a project to the new minimal api of ASP.Net 6.

Right now I have something similar to this:

builder.MapGet("/hello", CiaoCiao);

IResult CiaoCiao()
{
    return Results.Ok("Ciao ciao!");
}

The reason for having the endpoint implementation in a separate function is that I want to write a unit test for it. But I'm having the following issue:

How do I get the response value (in this case the string "Ciao ciao!") out of the IResult?

So far I didn't find anything in the official documentation about that. There is a class Microsoft.AspNetCore.Http.Result.OkObjectResult which I could cast to. But that's internal to AspNetCore, so it's not accessible from my unit test project.

Giacomo d'Antonio
  • 2,215
  • 3
  • 19
  • 23

3 Answers3

4

This isn't possible with ASP.NET Core 6 as all the implementations of IResult are internal.

This is planned to be improved as part of ASP.NET Core 7.

Integration testing your code via the HTTP interface would be one way of testing your application's endpoints with ASP.NET Core 6 using the WebApplicationFactory<T> class (docs).

Martin Costello
  • 9,672
  • 5
  • 60
  • 72
  • Thanks. There is a workaround I can use in the github issue. It will do for now. I'm aware of WebApplicationFactory, but that would require to set up authorization. A bit too cumbersome for a unit test. – Giacomo d'Antonio Mar 02 '22 at 13:12
4

I was able to come up with a workaround using some code in the github issue linked by Martin Costello in his answer:

private static async Task<T?> GetResponseValue<T>(IResult result)
{
    var mockHttpContext = new DefaultHttpContext
    {
        // RequestServices needs to be set so the IResult implementation can log.
        RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider(),
        Response =
        {
            // The default response body is Stream.Null which throws away anything that is written to it.
            Body = new MemoryStream(),
        },
    };

    await result.ExecuteAsync(mockHttpContext);

    // Reset MemoryStream to start so we can read the response.
    mockHttpContext.Response.Body.Position = 0;
    var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
    return await JsonSerializer.DeserializeAsync<T>(mockHttpContext.Response.Body, jsonOptions);
}

Ugly, but seems to work.

Giacomo d'Antonio
  • 2,215
  • 3
  • 19
  • 23
  • Isnt be easier and much more readable and mantainable just to create a controller action? – Serge Mar 02 '22 at 17:09
  • @Serge not sure I understand your comment. What do you mean? You don't have controllers with the minimal API. – Giacomo d'Antonio Mar 03 '22 at 12:59
  • No, you don' t have, But the pourpose of minimal Api is one line code. If it is more than one line then it is easier to create a controller, then to use your hundred lines code. Min api makes sense only if you have one line of very simple code. – Serge Mar 03 '22 at 13:14
  • 1
    I disagree. Minimal API you can have more complicated APIs its simply removing a lot of the scaffolding and simplifying it. See this post from Nick Chapas https://www.youtube.com/watch?v=4ORO-KOufeU the code examples by Microsoft are really poor. – Dr Schizo May 03 '22 at 20:42
1

It's also possible to do something simple like :

        [Fact]
        public void Then_it_returns_bad_request_result() =>
            _result.ToString().Should().EndWith("BadRequestObjectResult");

Not particularly satisfying, but does the job until I can investigate a better alternative using .NET7.

Ben Wesson
  • 589
  • 6
  • 16