UnitTest HttpResponse WriteAsync 和 CopyToAsync

UnitTest HttpResponse WriteAsync and CopyToAsync

我想对下一个方法进行单元测试:

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

最后两行来自

当前的单元测试实现是:

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

单元测试由于 NullReferenceException 而失败,一旦测试命中“await response.WriteAsync(json);”代码行就会引发该异常。你能指出正确的方向来修复这个异常,这样测试就会通过吗?

总结:单元测试需要检查给定的 'TestMessage' 是否实际写入了响应的正文。

背景资料: 我正在调用 SetResponseBody 方法,以便在 AddOpenIdConnect.

引发“OnRedirectToIdentityProvider”事件后立即修改响应主体
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

抽象 HttpResponse 需要配置太多内部结构才能在模拟时按预期工作。

我建议使用 DefaultHttpContext 并提取在该上下文中创建的默认响应。

[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
    //...
}

对于断言,提取响应主体的内容并断言其预期行为。

我们不能为所有方法,尤其是系统库编写单元测试。 所以解决方案是制作虚函数并使用下面的方法而不是直接等待 response.WriteAsync(json);

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

然后

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