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!
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.
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());
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(....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With