71

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!

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
jonsidnell
  • 1,210
  • 1
  • 10
  • 20
  • If your goal is to only test the return, you can force your Mock framework to ignore the argument: Mock.Arrange(() => repo.AllIncluding(x => x.Category == category)) .IgnoreArguments() .Returns((new List{new Product()}) .AsQueryable()); At the example above I am using a free version of JustMock. – Fernando Vezzali Nov 22 '13 at 14:15

3 Answers3

88

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.

Aasmund Eldhuset
  • 37,289
  • 4
  • 68
  • 81
  • That gave me the way to do it to at least get some green lights for both the UniqueCompanyAccepted and DuplicateCompanyRejected tests :) – jonsidnell Mar 04 '11 at 17:22
  • 1
    This is exactly what I was after but any chance the '...' section could further be extended as per your comments in your answer above Aasmund??? i.e actually try to match the company being passed into the expression? – IbrarMumtaz May 28 '12 at 08:10
  • 10
    @IbrarMumtaz: If you have a list `companies` that contains all of the companies, then `.Returns((Expression> predicate) => companies.Where(predicate));` should work. However, if you need more advanced functionality than this, you might be better off writing a class that implements `IRepository` rather than using Moq, since building complicated behaviour into mocks is cumbersome. – Aasmund Eldhuset May 28 '12 at 14:42
9

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());
Mark Coleman
  • 40,542
  • 9
  • 81
  • 101
  • Only saw this after marking the other question as the answer, but also would've been helpful. Thanks! – jonsidnell Mar 04 '11 at 17:23
  • 9
    The drawback of using `It.IsAny` in combination with a `Return()` that doesn't check the parameters is that you don't get to verify that the parameters are what they should be. Sometimes, of course, you really do wish to accept any parameter value, but in this case, you will probably want to verify that `SignupLogic` constructs its query correctly (using the company name, not some other property). – Aasmund Eldhuset Mar 04 '11 at 17:33
  • It.IsAny will always pass. I prefer to check lambda expression equality `m.FindBy(item=>item.CompanyID == 1)`. A way to accomplish this is to use LambdaCompare: https://stackoverflow.com/questions/283537/most-efficient-way-to-test-equality-of-lambda-expressions. By using this comparison class, new setup would be `Expression> testExpression = item => item.CompanyID == 1;repoMock.Setup(m => m.FindById(It.Is>>(criteria => LambdaCompare.Eq(criteria, testExpression)))).Returns(yourCompanyList)` – Junior Mayhé Sep 03 '17 at 16:34
1

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(....
Aliostad
  • 80,612
  • 21
  • 160
  • 208
  • 6
    He _is_ mocking a type he owns - `Repository`. The `Expression` isn't a mock; it is only used for matching the calls on the mock. – Aasmund Eldhuset Mar 04 '11 at 17:06
  • The title of question is `Moq'ing Expression> parameters` – Aliostad Mar 04 '11 at 17:12
  • Fair comment Aliostad, the title is somewhat unclear, though I'd like to think the rest of the question made my meaning clear - it was after all a fairly detailed, code-sampled question, even if I do say so myself :) Even so, the question was really "Moq'ing *Expression parameters*", where I'd tried to show it's where the Expression is being passed in as a parameter to a method. But, I've edited the title to make it clearer :) – jonsidnell Mar 04 '11 at 17:26
  • Well, my answer was correct and was earliest and no points for me :( – Aliostad Mar 04 '11 at 17:31
  • In fairness, I didn't include the .Returns in code samples because my question was already getting quite code heavy. I did, however, say the mock was config'd to "return an in-memory IEnumberable containing a Company object with name set to "Company Inc", so while you gave correct information that .Returns() is req'd, it wasn't the info the question requested. Aasmund did supply that, and his reply led me to the solution I've now got sitting on my hard drive. Thanks for answering though :) – jonsidnell Mar 04 '11 at 17:43
  • 1
    I tried this way but it doesn't work when I use Verify: Result Message: Moq.MockException : Expected invocation on the mock once, but was 0 times: u => u.Users.Any(a => a.Email == .user.Email) Configured setups: u => u.Users.Any(a => a.Email == .user.Email), Times.Never – Akira Yamamoto Oct 13 '14 at 14:23
  • 1
    @Aliostad your answer is old but it cant work. Moq returns a exception for UnsupportedException for Expression. Expressions must be passed with "It.Is/It.IsAny" – eL-Prova Mar 23 '17 at 09:49