如何为在循环内调用的此方法编写单元测试?

How do I write a unit test for this method that is called inside a loop?

我正在尝试使用 xUnit 和 Moq 测试一种方法。 public void DoSomeWork_Verify_GetJsonDataString_is_called() 测试失败。请有人告诉我我在 test/mocking 中遗漏了什么。 GetJsonDataString 是一个 API 调用 returns 一个 JSON 字符串。

public class A 
{
    private readonly IData _data;
    private readonly IApiCall _apiCall;
       
    public A (IData data, IApiCall apiCall)
    {
        _data = data;
        _apiCall = apiCall;
    }
       
    public void DoSomeWork
    {
        var apps = _data.GetListOfApps(); // test on this method call is passing
        foreach(var app in apps)
        {
            if(string.IsNullOrEmpty(app.AppId)) continue;
            string jsonString = _apiCall.GetJsonDataString("ApiEndpoint" + app.AppId); 
            // how do I test this method call? Note: I'm not using async, this is just a console app.
        }       
    }
}

//MyTest
public class TestA
{
    private readonly Mock<IData> _data;
    private readonly Mock<IApiCall> _apicall;

    public TestA()
    {
        _data = new Mock<IData>;
        _apiCall = new Mock<IApiCall>;
    }

    public void DoSomeWork_Verify_GetListOfApps_is_called() 
    {
        _data.Setup(x => x.GetListOfApps());
        var sut = new A(_data.Object, _apiCall.Object);
        sut.DoSomeWork();
        _data.Verify(x => x.GetListOfApps(), Times.AtLeastOnce);  // this passes
    }

    public void DoSomeWork_Verify_GetJsonDataString_is_called()
    {
        _apicall.Setup(x => x.GetJsonDataString(It.IsAny<string>()));
        var sut = new A(_data.Object, _apiCall.Object);
        sut.DoSomeWork();
        _apicall.Verify(x => x.GetJsonDataString(It.IsAny<string>()), Times.AtLeastOnce);  // Failing
    }  
} 

你需要模拟 GetListOfApps 以便它实际上 return 是你可以循环的东西。为此,您需要使用 Returns 方法。

假设方法 return 是 List<App> 您的设置可能如下所示:

_data.Setup(x => x.GetListOfApps())
     .Returns(new List<App> { new App { AppId = "X" } });

现在您的方法将接收要循环的数据,并且将调用您尝试验证的方法。

请注意,在设置 GetJsonDataString 时,您并未为其指定 return 值。默认情况下,最小起订量将 return null 但您可能也应该使用 return 值来模拟它。

_data.Setup(x => x.GetJsonDataString(It.IsAny<string>()))
     .Returns("some string");

还有 Returns 的重载接受工厂函数并将传递给模拟函数的参数作为输入。如果 want/need return 结果根据输入参数而变化,则可以使用它。

_data.Setup(x => x.GetJsonDataString(It.IsAny<string>()))
     .Returns(strParam => "some string" + strParam);

现在,当您进行验证时,我建议您尽量不要使用 It.IsAny 。否则你就是在说你不关心如何它被调用。假设我们想验证它实际上是用上面的模拟值 (AppId="X") 调用的,你会这样做:

_apicall.Verify(x => x.GetJsonDataString("ApiEndPointX"), Times.Once);

现在您得到了一个测试,它不仅检查该方法是否被调用,而且实际验证它是否使用 预期 值调用。

旁注:您的测试 class 没有理由在其构造函数中接受参数,尤其是 Mock<> 类型的参数。你甚至没有使用它们!从技术上讲,这甚至应该无法执行,因为您没有设置 IClassFixture 。您可以完全摆脱构造函数(并直接分配字段)或只删除参数:

public TestA()
{
    _data = new Mock<IData>();
    _apiCall = new Mock<IApiCall>();
}