4

I have a setup using Ninject and Moq in my solution. I use Entity Framework, and use the FakeDbSet implementation (see below). This allows me to get GetById, Create, Update and other methods to work, because of the way I've implemented them.

All my services have a method such as:

List<Invoice> GetBySpecification(InvoiceSpecification specification);

This is the only one I cannot easily mock, because my implementation goes something like this where I use the DbContext and use Where statements.

 public int GetBySpecification(InvoiceSpecification specification)
        {
            IQueryable<Invoice> query = BuildQuery(specification);
            return query.Count();
        }

        public IQueryable<Invoice> BuildQuery(InvoiceSpecification specification)
        {
            IQueryable<Creditor> query = _db.Creditors;

            if (!string.IsNullOrWhiteSpace(specification.Query))
            {
                var search = specification.Query.ToLower().Trim();
                query = query.Where(c => c.OfficeEmail.Contains(search)
                    || c.OfficePhone.Contains(search)
                    || c.CompanyRegistrationNumber.Contains(search)
                    || c.CompanyName.Contains(search)
                    || c.LastName.Contains(search)
                    || c.FirstName.Contains(search));
            }
            if (!string.IsNullOrWhiteSpace(specification.CompanyRegistrationNumber))
            {
                var search = specification.CompanyRegistrationNumber.ToLower().Trim();
                query = query.Where(c => c.CompanyRegistrationNumber == search);
            }
            if (specification.UpdateFrequency.HasValue)
            {
                query = query.Where(c => c.UpdateFrequency == specification.UpdateFrequency.Value);
            }

            return query.Where(c => !c.DateDeleted.HasValue);
        }

My question:

I would like to be able to use a SetUp when I run my classes. I would like to test my GetBySpecification and BuildQuery methods, and it's not uncommon that I use these methods in other methods.

I would love to be able to run a SetUp method, providing some "base database" in memory using C# objects I populate into a list, so when I use _db.Creditors, it returns a custom list of creditors I've setup and then use the queries on that one.

I think I'm quite far, but is not completely sure how I go on from here. I guess I need to update my Resolver / FakeDb set somehow, but I would really appreciate someone to help me in the right direction.

My Ninject Resolver:

    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<ILikvidoWebsitesApiContext>().ToProvider(new MoqContextProvider());
        // other awesome stuff
    }

My MoqContextProvider:

 public class MoqContextProvider : Provider<ILikvidoWebsitesApiContext>
    {
        protected override ILikvidoWebsitesApiContext CreateInstance(IContext context)
        {
            var mock = new Mock<ILikvidoWebsitesApiContext>();

            mock.Setup(m => m.Creditors).Returns(new FakeDbSet<Creditor>());
            return mock.Object;
        }
    }

FakeDbSet implementation:

public class FakeDbSet<T> : DbSet<T>, IDbSet<T> where T : class
{
    List<T> _data;

    public FakeDbSet()
    {
        _data = new List<T>();
    }

    public override T Find(params object[] keyValues)
    {
        var keyProperty = typeof(T).GetProperty(
            "Id",
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
        var result = this.SingleOrDefault(obj =>
            keyProperty.GetValue(obj).ToString() == keyValues.First().ToString());
        return result;
    }

    public override T Add(T item)
    {
        _data.Add(item);

        // Identity incrementation flow
        var prop = item.GetType().GetProperty("Id", typeof(int));
        if (prop != null)
        {
            var value = (int)prop.GetValue(item);
            if (value == 0)
            {
                prop.SetValue(item, _data.Max(d => (int)prop.GetValue(d)) + 1);
            }
        }
        return item;
    }

    public override T Remove(T item)
    {
        _data.Remove(item);
        return item;
    }

    public override T Attach(T item)
    {
        return null;
    }

    public T Detach(T item)
    {
        _data.Remove(item);
        return item;
    }

    public override T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public new TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public new List<T> Local
    {
        get { return _data; }
    }

    public override IEnumerable<T> AddRange(IEnumerable<T> entities)
    {
        _data.AddRange(entities);
        return _data;
    }

    public override IEnumerable<T> RemoveRange(IEnumerable<T> entities)
    {
        for (int i = entities.Count() - 1; i >= 0; i--)
        {
            T entity = entities.ElementAt(i);
            if (_data.Contains(entity))
            {
                Remove(entity);
            }
        }

        return this;
    }

    Type IQueryable.ElementType
    {
        get { return _data.AsQueryable().ElementType; }
    }

    Expression IQueryable.Expression
    {
        get { return _data.AsQueryable().Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return _data.AsQueryable().Provider; }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }
}
Lars Holdgaard
  • 9,496
  • 26
  • 102
  • 182
  • 1
    https://stackoverflow.com/questions/5609508/asp-net-mvc3-and-entity-framework-code-first-architecture/5610685#5610685 might be helpful. Unit testing EF sometimes has diminishing returns and an integration test may be more helpful. – Alex Terry May 14 '18 at 19:55
  • @ATerry I don't disagree if you're just testing EF, but I do have quite a lot of nice logic I want to test here. The "real" solution would maybe be to implement proper repositories, but at this stage I want to avoid it (so much extra code) :-) – Lars Holdgaard May 15 '18 at 07:09
  • There are some pretty nice frameworks that implement generic repos. This would cut down dramatically on the additional code you write. I have used this in past life https://github.com/urfnet/URF.NET. It only has a provider for EF though. There are a number of others on github with several providers, mongo, casandra, etc. – Alex Terry May 15 '18 at 13:57
  • You are going to need to setup/mock the `IQueryable` instances of DbSet as explained and detailed by this [MSDN article](https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx#). There are 2 types, one for standard queryies and another for async queries. IMHO it would be easier to stand up a unit/integration test database and seed/setup data that database than it is to mock EF. – bman7716 May 17 '18 at 16:16

1 Answers1

3

I don't understand why EntityFramework is even involved in this method's Unit Tests.

There are three types of injection in C#:

  1. Constructor Injection
  2. Method Injection <-- Use this one
  3. Property Injection

If you inject the IQueryable, then you will decouple this method from Entity Framework. Your logic is now testable without mocking EF.

Have your first method do this:

    public int GetBySpecification(InvoiceSpecification specification)
    {
        IQueryable<Invoice> query = BuildQuery(specification, _db.Creditors);
        return query.Count();
    }

Your second method now allows for injecting the queryable. You no longer need EF to be involved in the logic test.

    public IQueryable<Invoice> BuildQuery(InvoiceSpecification specification, IQueryable<Creditor> query)
    {
        if (!string.IsNullOrWhiteSpace(specification.Query))
        {
            var search = specification.Query.ToLower().Trim();
            query = query.Where(c => c.OfficeEmail.Contains(search)
                || c.OfficePhone.Contains(search)
                || c.CompanyRegistrationNumber.Contains(search)
                || c.CompanyName.Contains(search)
                || c.LastName.Contains(search)
                || c.FirstName.Contains(search));
        }
        if (!string.IsNullOrWhiteSpace(specification.CompanyRegistrationNumber))
        {
            var search = specification.CompanyRegistrationNumber.ToLower().Trim();
            query = query.Where(c => c.CompanyRegistrationNumber == search);
        }
        if (specification.UpdateFrequency.HasValue)
        {
            query = query.Where(c => c.UpdateFrequency == specification.UpdateFrequency.Value);
        }

        return query.Where(c => !c.DateDeleted.HasValue);
    }

Give that a try.

Another refactor idea . . . Even better would be to move QueryBuilder into its own object.

 public interface IInvoiceSpecificationQueryBuilder
 {
     IQueryable<Invoice> BuildQuery(InvoiceSpecification specification, IQueryable<Creditor> query)
 }

 public class InvoiceSpecificationQueryBuilder : IInvoiceSpecificationQueryBuilder
 {
     public IQueryable<Invoice> BuildQuery(InvoiceSpecification specification, IQueryable<Creditor> query)
     {
        // method logic here
     }
 }

Now you can use any of the three types of injection to inject the IInvoiceSpecificationQueryBuilder into the class that hosts the GetBySpecification() method.

For testing GetBySpecification, you only need to test that BuildQuery was called with the correct parameters.

Mocking EF (not ideal but still an option)

If you are dead-set on mocking entity framework, then bm7716 gave you a nice article. I implemented a generic implementation of the code in that article here: https://www.rhyous.com/2015/04/10/how-to-mock-an-entity-framework-dbcontext-and-its-dbset-properties. You are welcome to give that a try. There is a bug with later versions of Moq, so go back to Moq 4.7 to avoid it.

The better option is to remove/decouple EF from your logic.

Rhyous
  • 6,510
  • 2
  • 44
  • 50