UnitTest HttpResponse WriteAsync and CopyToAsync

Viewed 647

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

2 Answers

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.

We can't write unit test for all method especially system library. so solution is make virtual function and use below method instead of direct await response.WriteAsync(json);

public virtual async Task  WriteAsync(string json)
{
    await response.WriteAsync(json);
}

and then

yourClassObject_Where_SetResponseBody_Exist.Setup(m => m.WriteAsync
       (It.IsAny<string>()));
Related