.Net Core NullReferenceException 使用 ControllerBase.ValidationProblem() 时

kub*_*wlo 0 c# moq xunit asp.net-core

我正在为我的控制器中的用户创建方法编写单元测试。当我运行单元测试时,它return ValidationProblem();在我的控制器方法的行中返回 NullReferenceException 。

[xUnit.net 00:00:01.16]     WotkTimeManager.Tests.UsersControllerTests.PostUsers_BadResult_WhenInvalidData [FAIL]
  X WotkTimeManager.Tests.UsersControllerTests.PostUsers_BadResult_WhenInvalidData [285ms]
  Error Message:
   System.NullReferenceException : Object reference not set to an instance of an object.
  Stack Trace:
     at Microsoft.AspNetCore.Mvc.ControllerBase.ValidationProblem(String detail, String instance, Nullable`1 statusCode, String title, String type, ModelStateDictionary modelStateDictionary)
   at Microsoft.AspNetCore.Mvc.ControllerBase.ValidationProblem(ModelStateDictionary modelStateDictionary)
   at Microsoft.AspNetCore.Mvc.ControllerBase.ValidationProblem()
   at WorkTimeManager.Controllers.UsersController.Post(UserCreateDto user) in /mnt/c/Users/kubw1/WorkTimeManagerSolution/src/WorkTimeManager/Controllers/UsersController.cs:line 72
   at WotkTimeManager.Tests.UsersControllerTests.PostUsers_BadResult_WhenInvalidData() in /mnt/c/Users/kubw1/WorkTimeManagerSolution/test/WotkTimeManager.Tests/UsersControllerTests.cs:line 92
--- End of stack trace from previous location where exception was thrown ---
Run Code Online (Sandbox Code Playgroud)

我的控制器方法

        [HttpPost]
        public async Task<ActionResult<string>> Post(UserCreateDto user)
        {
            var userModel = _mapper.Map<User>(user);

            var result = await _userManager.CreateAsync(userModel, user.password);

            if (result.Succeeded)
            {
                return Ok();
            }
            else
            {
                foreach (var err in result.Errors)
                {
                    ModelState.AddModelError(err.Code, err.Description);
                }
                return ValidationProblem();
            }

        }
Run Code Online (Sandbox Code Playgroud)

单元测试

        [Fact]
        public async Task PostUsers_BadResult_WhenInvalidData()
        {
            var user = new UserCreateDto
            {
                username = "test",
                password = "testp",
                email = "email@wp.pl"
            };

            userManager
                .Setup(x => x.CreateAsync(It.IsAny<User>(), It.IsAny<string>()))
                .ReturnsAsync(IdentityResult.Failed(new IdentityError { Code = "Problem", Description = "Not working" })).Verifiable();

            controller = new UsersController(new UnitOfWork(dbContext), userManager.Object, mapper);

            var result = await controller.Post(user);

            Assert.IsType<ValidationProblemDetails>(result.Result);
        }
Run Code Online (Sandbox Code Playgroud)

Cod*_*ter 5

查看抛出的方法的来源

public virtual ActionResult ValidationProblem(
    string detail = null,
    string instance = null,
    int? statusCode = null,
    string title = null,
    string type = null,
    [ActionResultObjectValue] ModelStateDictionary modelStateDictionary = null)
{
    modelStateDictionary ??= ModelState;

    var validationProblem = ProblemDetailsFactory.CreateValidationProblemDetails(...);
Run Code Online (Sandbox Code Playgroud)

那个好像可以扔。那么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;
    }
}
Run Code Online (Sandbox Code Playgroud)

你没有HttpContext向你的控制器提供 an (如果你提供了,你没有注册 a ProblemDetailsFactory),所以确实,这个 getter 返回null,导致调用CreateValidationProblemDetails()抛出一个 NRE。

所以你需要提供它。ASP.NET 使用的 DefaultProblemDetailsFactory 是internal,所以你最好模拟它:

controller.ProblemDetailsFactory = new Mock<ProblemDetailsFactory>();
Run Code Online (Sandbox Code Playgroud)

然后设置您期望的呼叫。