2

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

Odrai
  • 2,163
  • 2
  • 31
  • 62
  • Maybe in the InnerException, you can see th line number of `SetResponseBody` that throw the error. – vernou Apr 02 '21 at 07:03
  • Why are you using the low-level HttpResponse class instead of returning an ActionResult? Your `SetResponseBody` method does the same thing `return Ok(new {message})` or `return Ok(new {Message=message})` would do, only worse - you can test an `ActionResult` result returned by an action. You can't test an `HttpResponse` – Panagiotis Kanavos Apr 02 '21 at 12:35
  • PS that `responseBody.CopyToAsync(originalResponseBody)` results in an invalid JSON string. So, what are you trying to do here? – Panagiotis Kanavos Apr 02 '21 at 12:37
  • `response.Body.Seek(0, SeekOrigin.Begin);` ???? This will result in garbage results. What are you trying to do? You don't have to test this method. It will always produce bad data – Panagiotis Kanavos Apr 02 '21 at 12:43
  • @PanagiotisKanavos I'm using AddOpenIdConnect and need to modify the response in case the OnRedirectToIdentityProvider event is raised. Within this event the response status is modified to e.g. 401 and I would like to set a custom message. To write this custom message, I've created the SetResponseBody method. The solution of [this post](https://stackoverflow.com/a/48216027/1974344) is used to set the response status, but I need to modify the Body as well. – Odrai Apr 02 '21 at 12:44
  • You aren't doing that though, you're overwriting the response and produce garbage – Panagiotis Kanavos Apr 02 '21 at 12:45
  • @PanagiotisKanavos Please have a look at the fist comment. Could you provide an alternative solution? – Odrai Apr 02 '21 at 12:48
  • 1
    An alternative to *what*? You didn't post anything relevant to OpenID, which is your real question. You didn't even mention the stack you use - Web API? Razor Pages? In both cases, if *your* code returns the response, you're free to return whatever you want. If you use a controller and want to return a 401 with a message, all you need to do is `return Unauthorized(new {message})` if not `return Unauthorized(message);`. – Panagiotis Kanavos Apr 02 '21 at 12:51
  • 1
    For Web API/MVC check the [ControllerBase.Unauthorized](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.unauthorized?view=aspnetcore-5.0#Microsoft_AspNetCore_Mvc_ControllerBase_Unauthorized_System_Object_) method. For Razor Pages check [PageModel.Unauthorized](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.razorpages.pagemodel.unauthorized?view=aspnetcore-5.0). If the response is returned by the *provider* though, you need to configure the provider – Panagiotis Kanavos Apr 02 '21 at 12:52
  • @PanagiotisKanavos I've modified the OP. Please have a look at the 'Background information' paragraph. I'm not able to return a Unauthorized object or even call a ControllerBase.Unauthorized method. – Odrai Apr 02 '21 at 13:06

2 Answers2

1

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.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
0

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>()));
Anish Sinha
  • 129
  • 11