Ok() 的部分模拟给了我 "Value cannot be null"。 (参数'result')

Partial mock of Ok() gives me "Value cannot be null". (Parameter 'result')

在 ASP.NET 核心 Web 应用程序 API 模板项目中,如果我创建部分模拟和 return 一个 OK(someObject),我总是会收到一条错误消息,提示“值不能是无效的。 (参数'result')'

有趣的是,当我 运行 应用程序 (F5) 时,它工作正常。

不起作用的是使用 Moq 的单元测试。然后我得到这个错误。

我正在使用:
.NET Core 3.0(我在使用 2.1 时也遇到这个错误)。
最新起订量4.13.1
最新的 xunit 2.4.0

控制器:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace MyTest.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : BaseAccessController
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController()
        {
        }

        [HttpGet]
        public async Task<ActionResult<WeatherForecast>> Get()
        {
            try
            {
                int userId = 3;

                if (HasAccess(userId) == false)
                    return Forbid();

                var rng = new Random();
                return Ok(Enumerable.Range(1, 5).Select(index => new WeatherForecast
                {
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = rng.Next(-20, 55),
                    Summary = Summaries[rng.Next(Summaries.Length)]
                })
                .ToArray()); // This is where the error happens!
            }
            catch (Exception ex)
            {
                throw;
            }
        }
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace MyTest.Controllers
{
    public class BaseAccessController : Controller
    {
        protected virtual bool HasAccess(int userId)
        {
            return true;
        }
    }
}

单元测试:

using System;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Moq.Protected;
using MyTest;
using MyTest.Controllers;
using Xunit;

namespace XUnitTestProject1
{
    public class UnitTest1
    {
        interface IBaseAccessControllerProtectedMembers
        {
            bool HasAccess(int userId);
        }

        [Fact]
        public async void MyUnitTest()
        {
            // Arrange

            var mockController = new Mock<WeatherForecastController>();

            mockController.Protected()
                .As<IBaseAccessControllerProtectedMembers>()
                .Setup(x => x.HasAccess(3))
                .Returns(true);

            // Act
            var result = await mockController.Object.Get(); 

            // Assert
            var actionResult = Assert.IsType<ActionResult<WeatherForecast>>(result);
            var returnValue = Assert.IsType<WeatherForecast>(actionResult.Value);
        }
    }
}

这是一个XY problem

首先,动作定义为return

public async Task<ActionResult<WeatherForecast>> Get()

然而没有等待并且它还尝试returnWeatherForecast

的集合
return Ok(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
    Date = DateTime.Now.AddDays(index),
    TemperatureC = rng.Next(-20, 55),
    Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray());

其次,如果使用 ActionResult<T>,则无需手动执行 Ok(..) 结果。只是 return 预期的对象。

引用Controller action return types in ASP.NET Core Web API

所以我首先建议重构该操作以遵循文档中建议的语法

[HttpGet]
public async Task<ActionResult<WeatherForecast[]>> Get() { //Note the array
    try {
        int userId = 3;

        if (HasAccess(userId) == false)
            return Forbid();

        var rng = new Random();
        WeatherForecast[] results = await Task.Run(() => Enumerable
            .Range(1, 5).Select(index => new WeatherForecast {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray()
        );
        return results;
    } catch (Exception ex) {
        throw;
    }
}

测试还应遵循正确的异步语法并声明预期的 return 类型

[Fact]
public async Task MyUnitTest() {
    // Arrange
    var mockController = new Mock<WeatherForecastController>() {
        CallBase = true; //so that it can call `HasAccess` without issue
    };

    // Act
    ActionResult<WeatherForecast[]> actionResult = await mockController.Object.Get(); 

    // Assert
    Assert.IsNotNull(actionResult);
    var returnValue = Assert.IsType<WeatherForecast>(actionResult.Value);
}

我只需要将 mockController 更改为:

var mockController = new Mock<PersonsController>(mockPersonsService.Object) { CallBase = true };

现在工作正常。谢谢!! :-)