5

I have a problem in my test case, I am trying to mock the return of my ICacheProvider but it always 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);
}

What is wrong in my test case?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
TiagoM
  • 3,458
  • 4
  • 42
  • 83

2 Answers2

2

The configured expectation for the arguments of the mocked method do not match what was passed to it, so it would return null.

You currently have an expectation of default(Func<IEnumerable<int>>) which would default to null, but in exercising the mock you pass an actual function which does not match the configured expectation.

Use Arg.Any as well for the second argument to make the mock expectation more flexible when being exercised.

cacheProvider
    .Fetch(Arg.Any<string>(), Arg.Any<Func<IEnumerable<int>>>())
    .ReturnsForAnyArgs(GetFakeCacheDB());
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Thanks for your answer, it worked when I change the signature of the method "HashSet returnEmpty()" to "IEnumerable returnEmpty()" – TiagoM Sep 27 '17 at 16:06
2

The reason the test fails is because the stubbed call is different to the one actually running. This is difficult to see because of the generic argument, but if you specify them explicitly you'll get something like this:

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

.NET sees Fetch<IEnumerable<int>>() and Fetch<HashSet<int>> as two different methods, so while the first line has been stubbed to return GetFakeCacheDB() for any arguments, the second method has not been configured and will return null. See this post for more explanation.

To get the test to work as you expect, make sure the generic call signatures match, either by explicitly specifying the generic, or making sure the arguments passed result in the correct generic.

// 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>();
}
David Tchepak
  • 9,826
  • 2
  • 56
  • 68