使用最小起订量和模拟设置的带有 IConfiguration 的单元测试控制器 returns null

Unit test controller with IConfiguration using Moq and Mock setup returns null

单元测试网站的新手 api。 我正在编写单元测试来测试控制器,我必须模拟 Iconfiguration。 appsettingsjson 有一个名为“AppSettings”的部分,我正在尝试模拟它。

此外,控制器中的 mock.setup returns 空值导致它失败。

这是我的控制器:

    private readonly ILogger _logger;
    private readonly IConfiguration _configuration;
    private readonly ICarPairingTable PairingTable;
    private readonly ICarDealerSettingsTable DealerSettingsTable;
    static AppSettings appSettings = null;
    public CarController(IConfiguration configuration, ICarPairingTable carPairingTable, ICarDealerSettingsTable settingsTable)
    {
        _configuration = configuration;
        appSettings = configuration.Get<AppSettingsModel>().AppSettings;
        PairingTable = carPairingTable;
        DealerSettingsTable = settingsTable;
    }
    
    [HttpGet]
    public ActionResult Get(string id){
        string DealerId ="";
        
            
                DealerId = PairingTable.GetDealerId(id).Result;
                if (string.IsNullOrEmpty(DealerId))
                {
                    result = new ReturnResult
                    {
                        status = "Fail",
                        data = "ID is invalid"
                    };
                    return NotFound(result);
                }
           
           
            
            SettingsInfo info = DealerSettingsTable.GetSettingsInfo(DealerId).Result;
            if (info == null)
            {
                result = new ReturnResult
                {
                    status = "Fail",
                    data = "Not Found"
                };
                return NotFound(result);

            }
            result = new ReturnResult
            {
                status = "Success",
                data = info
            };
            return Ok(result);
    }
    

这是我的单元测试:

   [Fact]
    public void Test1()
    {
        var mockConfig = new Mock<IConfiguration>();
        var configurationSection = new Mock<IConfigurationSection>();

        configurationSection.Setup(a => a.Value).Returns("testvalue");
        mockConfig.Setup(a => a.GetSection("AppSettings")).Returns(configurationSection.Object);

        var mock1 = new Mock<ICarPairingTable>();
       
        mock1.Setup(p => p.GetDealerId("456")).ReturnsAsync("123");
        var mock2 = new Mock<ICarDealerSettingsTable>();
        SettingsInfo mockSettings = new SettingsInfo()
        {
            DealerId = "123",
            
            Name="Dealer1"
        };
        mock2.Setup(p => p.GetSettingsInfo("123")).ReturnsAsync(()=>mockSettings);
        
        CarController controller = new CarController(mockConfig.Object,  mock1.Object, mock2.Object);
        var result = controller.Get("456");

        //Dont know what to assert

    }

写了单元测试,但不确定我的方法是否正确,不胜感激。

这更多是包裹在 XY problem 中的设计问题。

确实不应该注射IConfiguration。根据控制器如何使用配置,您应该做的是在启动时向服务集合注册设置

Startup.ConfigureServices

//...

AppSettings appSettings = Configuration.Get<AppSettingsModel>().AppSettings;

services.AddSingleton(appSettings);

//...

并将设置显式注入控制器

//...

private readonly AppSettings appSettings = null;
public CarController(AppSettings appSettings , ICarPairingTable carPairingTable, ICarDealerSettingsTable settingsTable) {
    this.appSettings = appSettings;
    PairingTable = carPairingTable;
    DealerSettingsTable = settingsTable;
}

//...

所以现在当单独对控制器进行单元测试时,您可以初始化所需的实例 class 并在进行单元测试时提供。

引用Explicit Dependencies Principle

您似乎也在混用异步等待和阻塞调用,例如 .Result

我建议你让动作一直异步

[HttpGet]
public async Task<ActionResult> Get(string id){
    string DealerId = await PairingTable.GetDealerId(id);
    if (string.IsNullOrEmpty(DealerId)) {
        var result = new ReturnResult {
            status = "Fail",
            data = "ID is invalid"
        };
        return NotFound(result);
    }

    SettingsInfo info = await DealerSettingsTable.GetSettingsInfo(DealerId);
    if (info == null) {
        var result = new ReturnResult {
            status = "Fail",
            data = "Not Found"
        };
        return NotFound(result);
    }
    var result = new ReturnResult {
        status = "Success",
        data = info
    };
    return Ok(result);
}

引用Async/Await - Best Practices in Asynchronous Programming

这样最终可以正确安排单元测试来验证预期的行为

[Fact]
public async Task Should_Return_Ok_ReturnRsult() {
    //Arrange
    var id = "456";
    var dealerId = "123";
    SettingsInfo expected = new SettingsInfo() {
        DealerId = dealerId,
        Name="Dealer1"
    };

    var pairingMock = new Mock<ICarPairingTable>();
    pairingMock.Setup(p => p.GetDealerId(id)).ReturnsAsync(dealerId);

    var dealerSettingsMock = new Mock<ICarDealerSettingsTable>();
    dealerSettingsMock.Setup(p => p.GetSettingsInfo(dealerId)).ReturnsAsync(() => expected);

    CarController controller = new CarController(new AppSettings(),  pairingMock.Object, dealerSettingsMock.Object);

    //Act
    var actionResult = await controller.Get(id);
    var actual = actionResult as OkObjectResult;

    //Assert (using FluentAssertions)

    actual.Should().NotBeNull();

    actual.Value.Should().BeOfType<ReturnResult>();

    var actualResult = actual.Value as ReturnResult;

    actualResult.data.Should().BeEquivalentTo(expected);        
}