简体   繁体   English

如何单元测试 Core MVC controller 操作是否调用 ControllerBase.Problem()

[英]How to unit test whether a Core MVC controller action calls ControllerBase.Problem()

We have a controller that derives from ControllerBase with an action like this:我们有一个 controller 派生自ControllerBase ,其操作如下:

public async Task<ActionResult> Get(int id)
{
  try
  {
    // Logic
    return Ok(someReturnValue);
  }
  catch
  {
    return Problem();
  }
}

We also have a unit test like this:我们还有这样的单元测试:

[TestMethod]
public async Task GetCallsProblemOnInvalidId()
{
  var result = sut.Get(someInvalidId);

}

But ControllerBase.Problem() throws a Null Reference Exception.但是ControllerBase.Problem()抛出 Null 引用异常。 This is a method from the Core MVC framework, so I don't realy know why it is throwing the error.这是 Core MVC 框架的一个方法,所以我真的不知道它为什么会抛出错误。 I think it may be because HttpContext is null, but I'm not sure.我想可能是因为 HttpContext 是 null,但我不确定。 Is there a standardized way to test a test case where the controller should call Problem() ?是否有标准化的方法来测试 controller 应该调用Problem()的测试用例? Any help is appreciated.任何帮助表示赞赏。 If the answer involves mocking: we use Moq and AutoFixtrue.如果答案涉及 mocking:我们使用 Moq 和 AutoFixtrue。

The null exception is because of a missing ProblemDetailsFactory null 异常是因为缺少ProblemDetailsFactory

In this case the controller needs to be able to create ProblemDetails instance via在这种情况下,控制器需要能够通过创建ProblemDetails实例

[NonAction]
public virtual ObjectResult Problem(
    string detail = null,
    string instance = null,
    int? statusCode = null,
    string title = null,
    string type = null)
{
    var problemDetails = ProblemDetailsFactory.CreateProblemDetails(
        HttpContext,
        statusCode: statusCode ?? 500,
        title: title,
        type: type,
        detail: detail,
        instance: instance);

    return new ObjectResult(problemDetails)
    {
        StatusCode = problemDetails.Status
    };
}

Source 来源

ProblemDetailsFactory is a settable property ProblemDetailsFactory是一个可设置的属性

public ProblemDetailsFactory ProblemDetailsFactory
{
    get
    {
        if (_problemDetailsFactory == null)
        {
            _problemDetailsFactory = HttpContext?.RequestServices?.GetRequiredService<ProblemDetailsFactory>();
        }

        return _problemDetailsFactory;
    }
    set
    {
        if (value == null)
        {
            throw new ArgumentNullException(nameof(value));
        }

        _problemDetailsFactory = value;
    }
}

Source 来源

that could be mocked and populated when testing in isolation.在单独测试时可以模拟和填充。

[TestMethod]
public async Task GetCallsProblemOnInvalidId() {
    //Arrange
    var problemDetails = new ProblemDetails() {
        //...populate as needed
    };
    var mock = new Mock<ProblemDetailsFactory>();
    mock
        .Setup(_ => _.CreateProblemDetails(
            It.IsAny<HttpContext>(),
            It.IsAny<int?>(),
            It.IsAny<string>(),
            It.IsAny<string>(),
            It.IsAny<string>(),
            It.IsAny<string>())
        )
        .Returns(problemDetails)
        .Verifyable();

    var sut = new MyController(...);
    sut.ProblemDetailsFactory = mock.Object;

    //...

    //Act
    var result = await sut.Get(someInvalidId);

    //Assert
    mock.Verify();//verify setup(s) invoked as expected

    //...other assertions
}

I came to this question via the related issue: https://github.com/dotnet/aspnetcore/issues/15166我是通过相关问题来回答这个问题的: https : //github.com/dotnet/aspnetcore/issues/15166

Nkosi correctly pointed to the background ProblemDetailsFactory. Nkosi 正确地指向了背景 ProblemDetailsFactory。

Note that the issue has been fixed in .NET 5.x but NOT in LTS .NET 3.1.x as you can see in the source code referenced by Nkosi (by switching the branches/tags in Github)请注意,该问题已在 .NET 5.x 中修复,但未在 LTS .NET 3.1.x 中修复,如您在 Nkosi 引用的源代码中所见(通过在 Github 中切换分支/标签)

As Nkosi said, the trick is to set the ProblemDetailsFactory property of your controller in your unit tests.正如 Nkosi 所说,诀窍是在单元测试中设置控制器的 ProblemDetailsFactory 属性。 Nkosi suggested to mock the ProblemDetailsFactory, but doing as above, you can't verify the values of the Problem object in your unit tests. Nkosi 建议模拟 ProblemDetailsFactory,但是按照上述操作,您无法在单元测试中验证 Problem 对象的值。 An alternative is simply to set a real implementation of the ProblemDetailsFactory, for instance copy the DefaultProblemDetailsFactory from Microsoft (internal class) to your UnitTest projects: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs Get rid of the options parameter there.另一种方法是设置 ProblemDetailsFactory 的真实实现,例如将 DefaultProblemDetailsFactory 从 Microsoft(内部类)复制到您的 UnitTest 项目: https : //github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc .Core/src/Infrastructure/DefaultProblemDetailsFactory.cs去掉那里的选项参数。 Then just set an instance of it in the controller in your unit test and see the returned object as expected!然后只需在单元测试的控制器中设置它的一个实例,并按预期查看返回的对象!

To improve upon EricBDev's answer (to avoid having to create any implementation of ProblemsDetailFactory in your tests) and Nkosi's answer (to allow verifying the values used when creating the Problem ), you can mock the ProblemsDetailFactory to return an empty ProblemsDetail (to avoid NRE) and then verify the calls to the mocked factory, to make sure the right status code, details, etc. are passed to it by the code under test.为了改进EricBDev 的答案(避免在测试中创建ProblemsDetailFactory的任何实现)和Nkosi 的答案(允许验证创建Problem时使用的值),您可以模拟ProblemsDetailFactory以返回一个空的ProblemsDetail (以避免 NRE)然后验证对模拟工厂的调用,以确保被测代码将正确的状态代码、详细信息等传递给它。

Example: (using Moq )示例:(使用Moq

// create the mock `ProblemDetailsFactory`
var problemDetailsFactoryMock = new Mock<ProblemDetailsFactory>();
// set it up to return an empty `Problems` object (to avoid the `NullReferenceException`s)
problemDetailsFactoryMock.Setup(p =>
    p.CreateProblemDetails(
        It.IsAny<HttpContext>(),
        It.IsAny<int>(),     // statusCode
        It.IsAny<string>(),  // title
        It.IsAny<string>(),  // type
        It.IsAny<string>(),  // detail
        It.IsAny<string>())  // instance
    ).Returns(new ProblemDetails());

// your other test code here

// verify the arguments passed to `Problem(...)`
_problemDetailsFactoryMock.Verify(p =>
    p.CreateProblemDetails(
        It.IsAny<HttpContext>(),
        (int)HttpStatusCode.Forbidden,  // or whatever StatusCode you expect
        default,                        // or whatever you expect for `Title`
        default,                        // or whatever you expect for `Type`
        It.Is<string>(s => s.Contains("whatever you expect in the Detail", StringComparison.OrdinalIgnoreCase)),
        default                         // or whatever you expect for `Instance`
    ));

In your tests, if you first create a ControllerContext, then ProblemDetails should be created as expected while executing controller code.在您的测试中,如果您首先创建一个 ControllerContext,那么在执行控制器代码时应该按预期创建 ProblemDetails。

...
MyController controller;

[Setup]
public void Setup()
{
    controller = new MyController();
    controller.ControllerContext = new ControllerContext
    {
        HttpContext = new DefaultHttpContext
        {
            // add other mocks or fakes 
        }
    };
}
...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何对MVC Controller动作进行单元测试,以调用与Controller关联的服务 - How to unit test MVC Controller Action that calls a service associated with the Controller 如何对使用HttpClient的MVC控制器操作进行单元测试 - How to unit test MVC controller action that is using HttpClient 如何在动作过滤器中使用参数验证对 MVC 控制器进行单元测试? - How to unit test MVC controller with argument validation in action filter? MVC控制器单元测试 - MVC Controller Unit Test DotNet Core,如何使用自定义 model 绑定对 Controller 操作方法进行单元测试 - DotNet Core, How do I unit test a Controller Action method with a custom model binding 如何在MVC中的动作的单元测试中模拟类 - How mock class in Unit Test for a Action in MVC 如何在 ASP.Net Core MVC 控制器中对 Catch 块进行单元测试? - How to Unit Test a Catch Block within a ASP.Net Core MVC Controller? 如何对具有私有方法的ASP.Net MVC控制器进行单元测试? - How to unit test ASP.Net MVC controller that has private methods in the action? 如何对依赖于c#中的身份验证的MVC控制器操作进行单元测试? - How to unit-test an MVC controller action which depends on authentication in c#? 如何在ASP.NET Core 1.1中对使用HttpContext的MVC控制器进行单元测试 - How to unit test MVC controller that uses HttpContext in ASP.NET Core 1.1
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM