Moq'ing methods where Expression<Func<T, bool>> are passed in as parameters

C#Unit TestingMoq

C# Problem Overview


I'm very new to unit testing and mocking! I'm trying to write some unit tests that covers some code that interacts with a data store. Data access is encapsulated by IRepository:

interface IRepository<T> {
    ....
    IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
    ....
}

The code that I'm trying to test, utilising a concrete IoC'd implementation of IRepository looks like this:

public class SignupLogic {
    private Repository<Company> repo = new Repository<Company>();
  
    public void AddNewCompany(Company toAdd) {
        Company existingCompany = this.repo.FindBy(c => c.Name == toAdd.Name).FirstOrDefault();
    
        if(existingCompany != null) {
            throw new ArgumentException("Company already exists");
        }

        repo.Add(Company);
        repo.Save();
    }
}

So that I'm testing the logic of SignupLogic.AddNewCompany() itself, rather than the logic and the concrete Repository, I'm mocking up IRepository and passing it into SignupLogic. The mocked up Repository looks like this:

Mock<Repository> repoMock = new Mock<Repository>();
repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc")....

which returns an in-memory IEnumberable containing a Company object with name set to "Company Inc". The unit test that calls SignupLogic.AddNewCompany sets up a company with duplicate details and trys to pass that in, and I assert that an ArgumentException is thrown with the message "Company already exists". This test isn't passing.

Debugging through the unit test and AddNewCompany() as it runs, it would appear that existingCompany is always null. In desperation, I've found that if I update SignupLogic.AddNewCompany() so that the call to FindBy looks like this:

Company existingCompany = this.repo.FindBy(c => c.Name == "Company Inc").FirstOrDefault();

the test passes, which suggests to me that Moq is only responding to code that is exactly the same as I've setup in my test fixture. Obviously that's not especially useful in testing that any duplicate company is rejected by SignupLogic.AddNewCompany.

I've tried setting up moq.FindBy(...) to use "Is.ItAny", but that doesn't cause the test to pass either.

From everything I'm reading, it would appear that testing Expressions as I'm trying to isn't actually do-able with Moq here. Is it possible? Please help!

C# Solutions


Solution 1 - C#

It is probably correct that only an Expression with the exactly same structure (and literal values) will match. I suggest that you use the overload of Returns() that lets you use the parameters the mock is called with:

repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())
        .Returns((Expression<Func<Company, bool>> predicate) => ...);

In ..., you can use predicate to return the matching companies (and maybe even throw an exception if the matching companies isn't what you expected). Not very pretty, but I think it will work.

Solution 2 - C#

You should be able to use It.IsAny<>() to accomplish what you are looking to do. With the use of It.IsAny<>() you can simply adjust the return type for your setup to test each branch of your code.

It.IsAny<Expression<Func<Company, bool>>>()

First Test, return a company regardless of predicate which will cause the exception to throw:

var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>{new Company{Name = "Company Inc"}});
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
//Assert the exception was thrown.

Second Test, make the return type an empty list witch will cause add to be called.:

var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>());
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
repoMock.Verify(r => r.Add(It.IsAny<Company>()), Times.Once());

Solution 3 - C#

You normally only mock the types you own. Those you do not own, really should not be mocked because of various difficulties. So mocking expressions - as the name of your question implies - is not the way to go.

In Moq framework. It is important to put .Returns() for functions otherwise it is not matched. So if you have not done that, it is your problem.

repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc").Returns(....

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjonsidnellView Question on Stackoverflow
Solution 1 - C#Aasmund EldhusetView Answer on Stackoverflow
Solution 2 - C#Mark ColemanView Answer on Stackoverflow
Solution 3 - C#AliostadView Answer on Stackoverflow