WillC
WillC

Reputation: 2115

Moq Func Parameter

I have a unit test using a Func parameter that I can't seem to get working with MOQ. Looking at other examples of Func parameters on StackOverflow, this is what I think should work:

Code to test:

public class PatternManager : IPatternManager
{
    private readonly ICacheManager _cacheManager;
    private readonly IDataRepo _data;

    public PatternManager(ICacheManager cacheManager, IDataRepo dataRepo)
    {
        _cacheManager = cacheManager;
        _data = dataRepo;
    }

    public List<Pattern> GetAllPatterns()
    {
        var allPatterns = _cacheManager.Get(() => _data.GetAllPatterns(), nameof(_data.GetAllPatterns));

        return allPatterns;
    }

    public Pattern GetBestPattern(int[] ids)
    {
        var patternList = GetAllPatterns().Where(w => w.PatternId > 1);

        ... More code...
    }
    ...
}


public interface ICacheManager
{
    T Get<T>(Func<T> GetMethodIfNotCached, string cacheName = null, int? cacheDurationInSeconds = null, string cachePath = null);

}

Instead of the list of Pattern that I expect from GetAllPatterns(), I am getting null.

[Test]
public void PatternManager_GetBestPattern()
{
    var cacheManager = new Mock<ICacheManager>();
    var dataRepo = new Mock<IDataRepo>();

    dataRepo.Setup(d => d.GetAllPatterns())
                    .Returns(GetPatternList());

    cacheManager.Setup(s => s.Get(It.IsAny<Func<List<Pattern>>>(), "", 100, "")) // Has optional params
                    .Returns(dataRepo.Object.GetAllPatterns());

    patternManager = new PatternManager(cacheManager.Object, dataRepo.Object);

    int[] pattern = { 1, 2, 3};

    var bestPattern = patternManager.GetBestPattern(pattern);

    Assert.AreEqual(1, bestPattern.PatternId);
}

private static List<Pattern> GetPatternList()
{ 
    ... returns a list of Pattern
}

What is wrong with this setup?

Upvotes: 2

Views: 1290

Answers (1)

Nkosi
Nkosi

Reputation: 247018

The mock is returning null by default because the setup is different to what is actually passed when the mocked member is invoked.

Look at what is invoked in the member under test

//...

var allPatterns = _cacheManager.Get(() => _data.GetAllPatterns(), nameof(_data.GetAllPatterns));

//...

which would most likely be a Func<List<Pattern>> and the string "GetAllPatterns" with the other optional parameters defaulting to the optional defaults.

Now look at what was set up in the test

//...

cacheManager
    .Setup(s => s.Get(It.IsAny<Func<List<Pattern>>>(), "", 100, "")) // Has optional params
    .Returns(dataRepo.Object.GetAllPatterns());

//...

Empty strings and an actual int value is setup to be expected while that is not what the member under test is actually invoking.

The mock would therefore not know what to do with the actual values passed when exercising the test so would default to null.

Refactor the set up to accept any values for the respective parameters using It.IsAny<>

//...

cacheManager
    .Setup(s => s.Get<List<Pattern>>(It.IsAny<Func<List<Pattern>>>(), It.IsAny<string>(), It.IsAny<int?>(), It.IsAny<string>()))
    .Returns((Func<List<Pattern>> f, string cn, int? cd, string cp) => f()); //Invokes function and returns result

//...

and note the use of the delegate to capture the parameters in Returns. That way the actual function can be invoked as it would be done in the member under test.

Upvotes: 5

Related Questions