Question

I've spent a few days looking for solution which allows me to mock method parametrized by the Expression<Func<T, bool>>. I found this. But unfortunately it doesn't work when I want to test service method with string parameter, in example: public IEnumerable<Person> FindByName(string name), such as below:

using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace UnitTestProject
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var mock = new Mock<IRepository<Person>();

            mock.Setup(r => r.Find(AreEqual<Person>(p => p.FirstName.Equals("Justin")))).Returns(new[]
                {
                    new Person {FirstName = "Justin", LastName = "Smith"},
                    new Person {FirstName = "Justin", LastName = "Quincy"}
                });

            var personService = new PersonService(mock.Object);
            Person[] justins = personService.FindByName("Justin").ToArray();
            Person[] etheredges = personService.FindByName("Etheredge").ToArray();

            Debugger.Break();
        }

        static Expression<Func<T, bool>> AreEqual<T>(Expression<Func<T, bool>> expr)
        {
            return Match.Create<Expression<Func<T, bool>>>(t => t.ToString() == expr.ToString());
        }
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public interface IRepository<T>
    {
        IEnumerable<T> Find(Expression<Func<Person, bool>> predicate);
    }

    public class PersonService
    {
        readonly IRepository<Person> _repository;

        public PersonService(IRepository<Person> repository)
        {
            _repository = repository;
        }

        public IEnumerable<Person> FindByName(string name)
        {
            return _repository.Find(p => p.FirstName.Equals(name));
        }
    }
}

When debugger breaks, I expect that array justins would contain two items listed above and array etheredges would contain no items. Actually they both are empty arrays. I suspect it occurs because in FindByName method, string is not provided directly, but rather through variable name.

Do you have any idea how to solve that problem?

Was it helpful?

Solution

Assuming that you just want to test the Service's Find logic (and that you trust LINQ :-)), what you could do is just compile the incoming Expression predicate and execute the expression across a fake repository (viz pred => fakePeople.Where(pred.Compile()));):

  [TestMethod]
  public void TestMethod1()
  {
     var mock = new Mock<IRepository<Person>>();

     var fakePeople = new[]
          {
             new Person {FirstName = "Justin", LastName = "Smith"},
             new Person {FirstName = "Justin", LastName = "Quincy"},
             new Person {FirstName = "Joe", LastName = "Bloggs"}
          };

     mock.Setup(r => r.Find(It.IsAny<Expression<Func<Person, bool>>>()))
         .Returns<Expression<Func<Person, bool>>>(
             pred => fakePeople.Where(pred.Compile()));

     var personService = new PersonService(mock.Object);

     var searchForJustins = personService.FindByName("Justin");
     Assert.AreEqual(2, searchForJustins.Count());
     Assert.IsTrue(searchForJustins.Any(_ => _.LastName == "Quincy") 
           && searchForJustins.Any(_ => _.LastName == "Smith"));

     var searchForEtheredges = personService.FindByName("Etheredge");
     Assert.IsFalse(searchForEtheredges.Any());
  }

Minor, but the Repository code itself didn't compile - I've assumed you have a generic repo pattern:

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

   public class PersonService
   {
      readonly IRepository<Person> _repository;

      public PersonService(IRepository<Person> repository)
      {
         _repository = repository;
      }

      public IEnumerable<Person> FindByName(string name)
      {
         return _repository.Find(p => p.FirstName.Equals(name));
      }
   }

OTHER TIPS

The problem is that your setup is not matching the expression parameter. This is a difficulty with testing classes that make use of literal lambda expressions. You can't really match one delegate with another, except by matching parameters and return Type.

Even though the expression is executed within the repository, the service owns the expression and the service test fixture should test that the expression produces the correct result. By setting up to match the expression, you are basically constructing an elaborate change control test.

To test this correctly, you have to put data into the repository, let the repository run the expression over the data (which can itself be mocked or in-memory), and assert that you get the expected data in return.

**

Something else to consider is that your service has knowledge of the inner working of the repository by virtue of it passing in a literal lambda expression. What you can do to make everything SOLID is to abstract the expression out of the service and associate it more closely with the repository implementation. What happens if you decide to plug in a EnterprisePersonRepository that calls a WCF service looking for the Person?

I would add the expression to the repository implementation via a static property and use your IOC container to reference it. So when you register your PersonRepository with IRepository, you will also register the FindByName expression.

**

A final thought is that this type of hyper-abstraction and adherence to OO dogma can be self-defeating in a non-pedagogical setting. A lot of straightforward and well tested code could have been written over the course of the few days that you were looking for a solution to this problem.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top