NSubstitute ReturnsForAnyArgs返回null但不应该返回null

时间:2017-09-27 09:49:10

标签: c# unit-testing nsubstitute

我的测试用例存在问题,我试图模仿ICacheProvider的返回值,但它总是返回null

[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);
}

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

2 个答案:

答案 0 :(得分:1)

模拟方法的参数的配置期望与传递给它的内容不匹配,因此它将返回null。

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

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

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

答案 1 :(得分:1)

测试失败的原因是因为存根调用与实际运行的调用不同。由于泛型参数,这很难看到,但是如果你明确指定它们,你会得到这样的东西:

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

.NET将Fetch<IEnumerable<int>>()Fetch<HashSet<int>>视为两种不同的方法,因此虽然第一行已被存根以便为任何参数返回GetFakeCacheDB(),但第二种方法尚未配置,并且返回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>();
}