NSubstitute ReturnsForAnyArgs 返回 null 但不应该返回

NSubstitute ReturnsForAnyArgs is returning null but shouldn't

我的测试用例有问题,我正在尝试模拟 ICacheProvider 的 return 但它总是 returnsnull.

[Fact]
public void Raise_ShoultReturnTrue_IfItsInCache()
{
    var cacheProvider = Substitute.For<ICacheProvider>();
    cacheProvider.Fetch(Arg.Any<string>(), default(Func<IEnumerable<int>>)).ReturnsForAnyArgs(GetFakeCacheDB());

    //I was expecting the var below to contain the data from GetFakeCacheDB method
    var cacheProviderReturn = cacheProvider.Fetch("anything", returnEmpty); 

    //there is more stuff here but doesnt matter for the question    
}

private HashSet<int> returnEmpty()
{
    return new HashSet<int>();
}

private IEnumerable<int> GetFakeCacheDB()
{
    var cacheData = new List<int>()
                    {
                       57352,
                       38752
                    };    
    return cacheData;
}

public interface ICacheProvider
{
    void Add<T>(string key, T item);
    void Add<T>(string key, T item, DateTime? absoluteExpiry, TimeSpan? relativeExpiry);
    T Fetch<T>(string key, Func<T> dataLookup);
    T Fetch<T>(string key, Func<T> dataLookup, DateTime? absoluteExpiry, TimeSpan? relativeExpiry);
    void Remove<T>(string key);
}

我的测试用例有什么问题?

模拟方法参数的配置预期与传递给它的不匹配,因此它将 return 为空。

您当前的期望值是 default(Func<IEnumerable<int>>),默认值是 null,但在执行模拟时,您传递了一个与配置期望值不匹配的实际函数。

对第二个参数也使用Arg.Any,使模拟期望在执行时更加灵活。

cacheProvider
    .Fetch(Arg.Any<string>(), Arg.Any<Func<IEnumerable<int>>>())
    .ReturnsForAnyArgs(GetFakeCacheDB());

测试失败的原因是存根调用与实际调用不同运行。由于通用参数,这很难看到,但如果您明确指定它们,您将得到如下内容:

// Arrange
cacheProvider.Fetch<IEnumerable<int>>(/* ... */).ReturnsForAnyArgs(GetFakeCacheDB());
// Act
var cacheProviderReturn = cacheProvider.Fetch<HashSet<int>>("anything", returnEmpty); 

.NET 将 Fetch<IEnumerable<int>>()Fetch<HashSet<int>> 视为两种不同的方法,因此虽然第一行已针对任何参数存根为 return GetFakeCacheDB(),但第二个方法尚未配置,将 return null。有关更多说明,请参阅 this post

要使测试按预期进行,请确保通用调用签名匹配,方法是显式指定通用,或确保传递的参数产生正确的通用。

// Option 1: explicit generic
var cacheProviderReturn = cacheProvider.Fetch<IEnumerable<int>>("anything", returnEmpty);
// where `returnEmpty` is `Func<HashSet<int>>`

// Option 2: keep stubbed call the same, and change `returnEmpty` return type
// from `HashSet<int>` to `IEnumerable<int>` to make sure it results
// in correct generic being called.
private IEnumerable<int> returnEmpty() {
    return new HashSet<int>();
}