Search code examples
c#unit-testing.net-coremoqhttpresponse

UnitTest HttpResponse WriteAsync and CopyToAsync


I would like to unit test the next method:

public static async Task SetResponseBody(HttpResponse response, string message)
{   
    var originalResponseBody = response.Body;
    var responseBody = new MemoryStream();
    response.Body = responseBody;
    response.ContentType = "application/json";
    dynamic body = new { Message = message };
    string json = JsonSerializer.Serialize(body);
    await response.WriteAsync(json);
    response.Body.Seek(0, SeekOrigin.Begin);
    await responseBody.CopyToAsync(originalResponseBody);         
}

The last two lines are used from this post.

The current unit test implementation is:

[TestMethod]
public async Task SetResponseBody_TestMessageAsync()
{
    var expected = "TestMessage";
    string actual = null;
    var responseMock = new Mock<HttpResponse>();
    responseMock
        .Setup(_ => _.Body.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
        .Callback((byte[] data, int offset, int length, CancellationToken token) =>
        {
            if (length > 0)
                actual = Encoding.UTF8.GetString(data);
        })
        .Returns(Task.CompletedTask);
    await ResponseRewriter.SetResponseBody(responseMock.Object, expected);
}

The unit tests fails due to a NullReferenceException which is raised once the test hits the 'await response.WriteAsync(json);' line of code. Could you point me in the right direction in order to fix this exception, so the test will pass?

Summarized: The unit tests needs to check if the given 'TestMessage' is actually written to the Body of the response.

Background information: I'm calling the SetResponseBody method in order to modify the response body as soon as the 'OnRedirectToIdentityProvider' event is raised from AddOpenIdConnect.

OnRedirectToIdentityProvider = async e =>
{
    // e is of type RedirectContext
    if (e.Request.Path.StartsWithSegments("/api")))
    {            
        if (e.Response.StatusCode == (int)HttpStatusCode.OK)
        {
            e.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            // TestMessage is a const
            // e.Response is readonly (get) so it's not possible to set it directly.
            await ResponseRewriter.SetResponseBody(e.Response, TestMessage);
        }
        e.HandleResponse();
    }
    await Task.CompletedTask;
}

.NET Core 3.1, WebApi, OpenId


Solution

  • There are too many internals that need to be configured for the abstract HttpResponse to work as intended when mocking it.

    I would suggest using DefaultHttpContext and extracting the default response created within that context.

    [TestMethod]
    public async Task SetResponseBody_TestMessageAsync() {
        //Arrange
        string expected = "TestMessage";
        string actual = null;
        HttpContext httpContext = new DefaultHttpContext();
        HttpResponse response = httpContext.Response
        
        //Act
        await ResponseRewriter.SetResponseBody(response, expected);
        
        //Assert
        //...
    }
    

    for the assertion, extract the content of the response body and assert its expected behavior.