zzzprojects / EntityFramework-Classic

Entity Framework Classic is a supported version of the latest EF6 codebase. It supports .NET Framework and .NET Core and overcomes some EF limitations by adding tons of must-haves built-in features.
https://entityframework-classic.net
Other
103 stars 27 forks source link

Problem mocking DbSet and using Include #18

Closed pilarodriguez closed 5 years ago

pilarodriguez commented 5 years ago

I have this line of code inside a method:

var orders = dbContext.Customers.Include(s => s.Orders).ToList();

I'm trying to create unit tests for that method, but I get the following exception:

The member 'IEnumerable<TResult>.GetEnumerator' has not been implemented on type 'IncludeDbQuery`2' which inherits from 'DbSet`1'. Test doubles for 'DbSet`1' must provide implementations of methods and properties that are used.

I've tried providing an implementation of the DbSet for the tests:

var data = new List<CustomerEntity>
{
    new CustomerEntity
    {
        CustomerId = "CustomerId",
        Orders = new[]
        {
            new OrderEntity
            {
                CustomerId = "CustomerId",
                OrderName = "OrderName"
            }
        }
    }
}.AsQueryable();

var mockSet = new Mock<DbSet<CustomerEntity>>();
mockSet.As<IQueryable<CustomerEntity>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<CustomerEntity>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<CustomerEntity>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<CustomerEntity>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());

mockDbContext.Setup(c => c.Customers).Returns(mockSet.Object);

But now, I'm getting an Object reference not set to an instance of an object error.

What am I missing?

This solution is working using EntityFramework 6, but not with EF Classic. I know that Include now returns an IncludeDbQuery instead of an IQueryable , but I still don't know what mock I am missing.

JonathanMagnan commented 5 years ago

Hello @pilarodriguez ,

Thank you for reporting, we will look at it today.

Best Regards,

Jonathan

JonathanMagnan commented 5 years ago

Hello @pilarodriguez ,

That one has been a lot hard then we initially thought but we finally make it compatible with Moq.

The v7.0.24 has been released.

Let me know if everything works as expected.

If you need, we can also provide an example that works with Include.

Best Regards,

Jonathan

pilarodriguez commented 5 years ago

Thank you. Yes, it would be great it you could provide me some examples for Include

JonathanMagnan commented 5 years ago

Sure, you should get it later today.

JonathanMagnan commented 5 years ago

Hello @pilarodriguez ,

Here is an example

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Castle.Core.Logging;
using Moq;

namespace Z.Lab.Request_Mock
{
    public partial class Form_Request_QueryFilter_Interface : Form
    {
        public static int ApplicationTenantID = 1;

        public Form_Request_QueryFilter_Interface()
        {
            var customer = new Customer();
            var order = new Order() { Name = "blabla" };
            customer.Orders.Add(order);

            var customers = new List<Customer>
            {
                customer
            }.AsQueryable();

            var orders = new List<Order>
            {
                order
            }.AsQueryable();

            var mockContext = new Mock<EntityContext>(MockBehavior.Loose);

            var mockCustomersSet = new Mock<DbSet<Customer>>();
            mockCustomersSet.As<IQueryable<Customer>>().Setup(m => m.Provider).Returns(customers.Provider);
            mockCustomersSet.As<IQueryable<Customer>>().Setup(m => m.Expression).Returns(customers.Expression);
            mockCustomersSet.As<IQueryable<Customer>>().Setup(m => m.ElementType).Returns(customers.ElementType);
            mockCustomersSet.As<IQueryable<Customer>>().Setup(m => m.GetEnumerator()).Returns(customers.GetEnumerator());
            mockCustomersSet.Setup(m => m.Include("Orders")).Returns(mockCustomersSet.Object);

            var mockOrdersSet = new Mock<DbSet<Order>>();
            mockOrdersSet.As<IQueryable<Order>>().Setup(m => m.Provider).Returns(orders.Provider);
            mockOrdersSet.As<IQueryable<Order>>().Setup(m => m.Expression).Returns(orders.Expression);
            mockOrdersSet.As<IQueryable<Order>>().Setup(m => m.ElementType).Returns(orders.ElementType);
            mockOrdersSet.As<IQueryable<Order>>().Setup(m => m.GetEnumerator()).Returns(orders.GetEnumerator());

            mockContext.SetupGet(c => c.Customers).Returns(mockCustomersSet.Object);
            mockContext.SetupGet(c => c.Orders).Returns(mockOrdersSet.Object);

            var customerList = mockContext.Object.Customers.Include("Orders").Select(p => p).ToList();
        }

        public class EntityContext : DbContext
        {
            public EntityContext() : base(My.ConnectionString)
            {
            }

            public virtual DbSet<Customer> Customers { get; set; }
            public virtual DbSet<Order> Orders { get; set; }

        }

        public class Customer
        {
            public Customer()
            {
                Orders = new List<Order>();
            }

            public int CustomerID { get; set; }
            public string Name { get; set; }
            public string Description { get; set; }
            public Boolean IsDeleted { get; set; }
            public virtual List<Order> Orders { get; set; }
        }

        public class Order
        {
            public int OrderID { get; set; }
            public int CustomerID { get; set; }
            public string Name { get; set; }
            public string Description { get; set; }
            public Boolean IsDeleted { get; set; }
        }
    }
}

Let me know if you need more help on this issue.

Best Regards,

Jonathan

pilarodriguez commented 5 years ago

Hello @JonathanMagnan . Sorry, I was having some other issues with my code and I couldn't try the new package version.

The problem I have with your example is that we are not using dbContext.Customers.Include("Orders"). We are using dbContext.Customers.Include(s => s.Orders), which uses the public IncludeDbQuery<TResult, TProperty> Include<TProperty>(Expression<Func<TResult, TProperty>> path);. So I cannot mock it with mockCustomersSet.Setup(m => m.Include("Orders")).Returns(mockCustomersSet.Object);

JonathanMagnan commented 5 years ago

We will look at it today

JonathanMagnan commented 5 years ago

Hello @pilarodriguez ,

My developer tried it and it seems to works with both syntax.

We tried it with var customerList = mockContext.Object.Customers.Include(p => p.Orders).Select(p => p).ToList(); as you use and everything work.

Are we missing something in your last comment?

pilarodriguez commented 5 years ago

I finally have it working. The problem is that instead of var customerList = mockContext.Object.Customers.Include(p => p.Orders).Select(p => p).ToList();, I was trying something like var customerList = mockContext.Object.Customers.Include(p => p.Orders).ToList(); (without the Select), so I was getting an exception. I'm not sure why I have a problem if I just do a ToList after the Include

JonathanMagnan commented 5 years ago

Hello @pilarodriguez ,

We tried with the following code:

var customerList = mockContext.Object.Customers.Include("Orders").ToList();

And that still work for us, the Select was only here by mistake.

If everything works, could we close this request?

Best Regards,

Jonathan

pilarodriguez commented 5 years ago

Sure. Thanks a lot for your support

gloinho commented 1 year ago

Hey, I cant work this around... Something changed?