导语
在 ASP.NET Core 里,如果你想单元测试 HttpContext.Features.Get<SomeType>(),这个技巧一定不要错过。
问题
我有个 Error 页面,需要取得异常的详细信息。我使用 HttpContext.Features.Get<IExceptionHandlerPathFeature>() 方法。
public void OnGet()
{
var requestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionFeature is not null)
{
// Get which route the exception occurred at
var routeWhereExceptionOccurred = exceptionFeature.Path;
// Get the exception that occurred
var exceptionThatOccurred = exceptionFeature.Error;
_logger.LogError($"Error: {routeWhereExceptionOccurred}, " +
$"client IP: {HttpContext.Connection.RemoteIpAddress}, " +
$"request id: {requestId}", exceptionThatOccurred);
}
RequestId = requestId;
}
现在,我需要单元测试这段代码。通常,在需要 HttpContext的 Page 或 Controller 中,我会使用 DefaultHttpContext 的实例。但我发现 HttpContext 上的 Features 属性是只读的。因此没有办法将 mock 好的对象赋值给它。
namespace Microsoft.AspNetCore.Http
{
public abstract class HttpContext
{
protected HttpContext();
//
// Summary:
// Gets the collection of HTTP features provided by the server and middleware available
// on this request.
public abstract IFeatureCollection Features { get; }
// ...
}
}
解决办法
首先,像平常一样准备 mock。在我的案例里,我需要配置 IFeatureCollection.Get() 方法,返回我想要的对象。
var mockIFeatureCollection = _mockRepository.Create<IFeatureCollection>();
mockIFeatureCollection.Setup(p => p.Get<IExceptionHandlerPathFeature>())
.Returns(new ExceptionHandlerFeature
{
Path = "/996/icu",
Error = new("Too much fubao")
});
httpContextMock.Setup(p => p.Features).Returns(mockIFeatureCollection.Object);
下下来,为了给 HttpContext.Features 赋值,我们这次不能使用 DefaultHttpContext 了。我们需要创建 HttpContext 自己的 mock,并且配置 Features 属性返回刚才 mock 的 IFeatureCollection 对象。
var httpContextMock = _mockRepository.Create<HttpContext>();
httpContextMock.Setup(p => p.Features).Returns(mockIFeatureCollection.Object);
现在运行单元测试,我们可以看到正确的值已经输出了。