如何对异常处理程序中间件进行单元测试

2024-03-03

我正在尝试使用自定义错误处理程序为我的 .NET Core 3 API 返回格式正确的异常。处理程序工作得很好,我遇到的问题是编写适当的单元测试来测试处理程序。我为此注册了中间件,如下所示:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IEnvService envService)
    {
        if (envService.IsNonProductionEnv())
        {
            app.UseExceptionHandler("/Error-descriptive");
        }
        else
        {
            app.UseExceptionHandler("/Error");
        }

        ...

     }

以下是“错误描述”端点的代码:

    public IActionResult ErrorDescriptive()
    {
        if (!_env.IsNonProductionEnv())            
            throw new InvalidOperationException("This endpoint cannot be invoked in a production environment.");

        IExceptionHandlerFeature exFeature = HttpContext.Features.Get<IExceptionHandlerFeature>();

        return Problem(detail: exFeature.Error.StackTrace, title: exFeature.Error.Message);
     }

我具体处理的问题是这一行:

IExceptionHandlerFeature exFeature = HttpContext.Features.Get<IExceptionHandlerFeature>();

从单元测试的角度来看,我很难让它工作,因为(据我所知)这行代码从服务器获取了最新的异常。有没有一种方法可以在我的测试中设置从 HttpContext 获得的异常?另外,由于这是使用 HttpContext,我是否还需要合并它的模拟版本,例如 DefaultHttpContext?


您可以通过注入来实现这一点IHttpContextAccessor https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.ihttpcontextaccessor?view=aspnetcore-3.1控制器中的接口。 该接口提供了访问 HttpContext 对象的抽象,主要是在 Web 库之外。

要使用它,你必须services.AddHttpContextAccessor();在您的启动文件中。 然后你可以将它注入到你的控制器中。

[ApiController]
public class ErrorController
{
    private readonly ILogger<ErrorController> logger;
    private readonly IHttpContextAccessor httpContextAccessor;

    public ErrorController(ILogger<ErrorController> logger, IHttpContextAccessor httpContextAccessor){
        this.logger = logger;
        this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }
}

然后在您的方法中,您使用访问器接口中的 HttpContext 而不是基类。

[Route("/error-development")]
public IActionResult HandleErrorDevelopment() 
{
     var exceptionFeature = this.httpContextAccessor.HttpContext.Features.Get<IExceptionHandlerFeature>();
     var error = exceptionFeature.Error;
}

由于我们现在正在针对注入的接口进行工作,因此您可以在单元测试中模拟它。请记住,您必须模拟您正在调用的整个链。 (我在本例中使用 xUnit 和 Moq)。

public ErrorControllerTests() {
     this.httpContextAccessorMock = new Mock<IHttpContextAccessor>();
     this.httpContextMock = new Mock<HttpContext>();
     this.featureCollectionMock = new Mock<IFeatureCollection>();
     this.exceptionHandlerFeatureMock = new Mock<IExceptionHandlerFeature>();

     this.httpContextAccessorMock.Setup(ca => ca.HttpContext).Returns(this.httpContextMock.Object);
     this.httpContextMock.Setup(c => c.Features).Returns(this.featureCollectionMock.Object);
     this.featureCollectionMock.Setup(fc => fc.Get<IExceptionHandlerFeature>()).Returns(this.exceptionHandlerFeatureMock.Object);

     this.controller = new ErrorController(null, this.httpContextAccessorMock.Object);
}

[Fact]
public void HandleErrorDevelopment() {
    Exception thrownException;
    try{
        throw new ApplicationException("A thrown application exception");
    }
    catch(Exception ex){
        thrownException = ex;
    }
    this.exceptionHandlerFeatureMock.Setup(ehf => ehf.Error).Returns(thrownException);

    var result = this.controller.HandleErrorDevelopment();
    var objectResult = result as ObjectResult;
    Assert.NotNull(objectResult);
    Assert.Equal(500, objectResult.StatusCode);
    var problem = objectResult.Value as ProblemDetails;
    Assert.NotNull(problem);
    Assert.Equal(thrownException.Message, problem.Title);
    Assert.Equal(thrownException.StackTrace, problem.Detail);
}

重要的: 您不能使用ControllerBase.Problem()方法。该实现依赖于ControllerBase.HttpContext检索一个ProblemDetailsFactory object.

您可以在源代码 https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ControllerBase.cs。由于您的单元测试没有设置上下文,因此它会抛出NullReferenceException.

Factory只不过是创建一个的帮手ProblemDetails https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.problemdetails?view=aspnetcore-3.1目的。再次你可以看到GitHub 上的默认实现 https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs.

我最终创建了自己的私有方法来创建这个对象。

private ProblemDetails CreateProblemDetails(
     int? statusCode = null,
     string title = null,
     string type = null,
     string detail = null,
     string instance = null){

     statusCode ??= 500;
     var problemDetails = new ProblemDetails
     {
          Status = statusCode.Value,
          Title = title,
          Type = type,
          Detail = detail,
          Instance = instance,
     };

     var traceId = Activity.Current?.Id ?? this.httpContextAccessor.HttpContext?.TraceIdentifier;
     if (traceId != null)
     {
         problemDetails.Extensions["traceId"] = traceId;
     }

     return problemDetails;
}

完整的错误处理方法如下所示。

[Route("/error-development")]
public IActionResult HandleErrorDevelopment() {
      var exceptionFeature = this.httpContextAccessor.HttpContext.Features.Get<IExceptionHandlerFeature>();
      var error = exceptionFeature.Error;
      this.logger?.LogError(error, "Unhandled exception");
      var problem = this.CreateProblemDetails(title: error.Message, detail: error.StackTrace);
       return new ObjectResult(problem){
           StatusCode = problem.Status
       };
}

对于生产,我要么提供要向用户显示的通用消息,要么在已经有要显示的用户友好消息的代码中抛出自定义处理的异常。

if (error is HandledApplicationException)
{
    // handled exceptions with user-friendly message.
    problem = this.CreateProblemDetails(title: error.Message);
}
else {
    // unhandled exceptions. Provice a generic error message to display to the end user.
    problem = this.CreateProblemDetails(title: "An unexpected exception occured. Please try again or contact IT support.");
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何对异常处理程序中间件进行单元测试 的相关文章

随机推荐

  • Magento API 的创建发票方法无法正常工作

    我正在尝试使用 XMLRPC 在 Android 应用程序中使用 Magento API 创建销售订单发票 我正在使用方法 sales order invoice create 用于创建发票 此方法在给定数量的响应中为我提供发票 ID 如中
  • 如何通过 API Laravel 处理用户注册

    我一直在阅读和观看一些有关 Laravel API 开发的教程 尽管我使用过一些 Laravel 但我对 API 开发完全是新手 从我读过的所有教程来看 它们处理的是 登录 获取一些数据 更新信息 删除信息 甚至将一些信息插入数据库 我的问
  • C# .NET 中的 Web 响应最多只能运行几次

    我正在使用 twitter api 开发一个应用程序 其中涉及编写一个方法来检查用户是否存在 这是我的代码 public static bool checkUserExists string user string URL https tw
  • 如何在 Struts 2 中向我的所有视图公开一个对象?

    我有一个使用 Struts 2 和 Freemarker 模板以及 Spring 4 的 Web 应用程序 我有一些配置字符串存储在 properties我需要在每个页面上呈现的文件 例如 我们的 CDN 路径 其中包含版本字符串 现在这些
  • OSError:libgdal.dylib:无法打开文件

    问题是 Docker 无法正常运行 因为OSError opt homebrew Cellar gdal 3 3 0 2 lib libgdal dylib cannot open shared object file No such fi
  • 使用 Pyx 绘制大括号

    如何使用 Pyx 在任意两个点之间绘制一条 支撑 线 它看起来像这样 大括号示例http tof canardpc com view d16770a8 0fc6 4e9d b43c a11eaa09304d http tof canardp
  • 如何编写一个以两个矩阵 A 和 B 作为输入并输出乘积矩阵 A*B 的函数?

    如何编写一个以两个矩阵 A 和 B 作为输入并输出乘积矩阵 A B 的函数 使用 MATLAB 带有循环和条件 我的尝试 function prodAB MultiplicoMatrices A B prod 0 prodAB for i
  • 发生特定情况时如何停止 Kotlin 流程

    如果代码中发生某些情况 我想取消 kotlin 流程 假设我有一个方法如下 fun test Flow
  • 自定义设备控制器不工作

    我有两个模型居民和用户 它们都包含 roll number 属性 我现在已经在驻留模型中输入了数据 当用户注册哪个是 Devise 资源时 它会检查驻留模型中是否存在相同的 roll number 然后就可以注册用户了 所以基本上我向 De
  • 替换JS中某个字符的所有实例?

    我正在尝试创建一个简单的函数来替换 JS 中字符串中某个字符的所有实例 在这种情况下 我想替换所有a s with o s 我很确定代码是正确的 但输出仍然是原始字符串 function replaceLetter string for v
  • mongorestore 随机崩溃(致命错误)

    我使用的是 macOS 10 12 mongod version db version v3 2 8 git version ed70e33130c977bda0024c125b56d159573dbaf0 OpenSSL version
  • 如何在源代码中查找搜索词

    我正在寻找一种在项目的 C C 代码中搜索给定术语的方法 同时忽略注释和字符串中出现的任何情况 由于代码库相当大 我正在寻找一种方法自动地识别与我的搜索词匹配的代码行 因为它们需要手动检查 如果可能的话 我想在我的 Linux 系统上执行搜
  • 绘制相同值时显示更大的点

    当我绘制以下示例时 Participant lt c 1 12 AnswersDay1 lt c 9 3 9 13 7 12 10 7 9 0 12 11 Day1Group lt c 0 1 0 1 0 1 0 1 0 1 0 1 Pus
  • 傅立叶空间中的滤波器的行为与预期不同

    这是我提出的已回答问题的后续内容 可以找到here https stackoverflow com questions 54022376 inverse fft returns negative values when it should
  • RDP(VM) 最小化时自动化脚本失败

    我一直面临着在其中一台虚拟机上自动执行脚本的问题 我已经实现了保存文档功能的自动化 该功能最好是 Windows 设计的 UI 我尝试过使用各种技术 工具 如 AutoIT Python Sikuli 但如果虚拟机最小化 脚本就会停止 如果
  • 使用 Savon 在 Ruby on Rails 中进行 SOAP 调用在信封和主要操作方面变得很奇怪

    在使用 Savon rb 的 Rails 项目中 我尝试进行非常复杂的 SOAP 调用 至少复杂到 Savon 构建者太麻烦了 所以我决定直接操作 xml 首先我启动客户端 client Savon client endpoint gt h
  • 有 XHTML 5 验证器吗?

    是否有专门针对 XHTML 5 的验证器 即 HTML 5 的 XML 序列化 这W3C 验证器 http validator w3 org 支持文档类型 HTML 5 experimental which treats as valid
  • 从第二次“应用内购买”开始在 Android 中抛出异常

    我正在尝试包含在应用程序购买中 并且已成功显示可用的 SKU 现在我想进行虚假购买 所以我使用了 appId android test purchased 第一次它工作完美 但从接下来它抛出异常 如下所示 尝试在空对象引用上调用虚拟方法 a
  • 为什么主键顺序很重要?

    我最近在 EntityFramework 项目中设置了一个类 它将其几个成员指定为组合键 但是 当需要从中创建数据库时 它给出了错误 无法确定类型 NNNNN 的复合主键排序 使用 ColumnAttribute 或 HasKey 方法指定
  • 如何对异常处理程序中间件进行单元测试

    我正在尝试使用自定义错误处理程序为我的 NET Core 3 API 返回格式正确的异常 处理程序工作得很好 我遇到的问题是编写适当的单元测试来测试处理程序 我为此注册了中间件 如下所示 public void Configure IApp