I am writing a unit test to test a method that gets data using Entity Framework and LINQ. I'm using mocked DbSet
s in my test. The method is returning data from the Orders
DbSet
, along with a navigation property from the related Customers
DbSet
, using the extension System.Data.Entity.Include
method.
I've run into a weird situation in which, depending on where I call Include
, either A) the test succeeds or B) I get an exception. Therein lies my question.
(This is a simplification of the actual code. I realize that these tests, as written below, are silly and pointless.)
public class Order
{
// Primary key
public string OrderId { get; set; }
// Foreign key to Customers table
public string CustomerId { get; set; }
// Navigation property
public virtual Customer Customer {get; set; }
}
public class Customer
{
// Primary key
public string CustomerId { get; set; }
}
public class MyContext : DbContext
{
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasKey(p => new { p.OrderId });
modelBuilder.Entity<Order>()
.HasRequired(p => p.Customer)
.WithMany();
modelBuilder.Entity<Customer>()
.HasKey(p => new { p.CustomerId });
}
// ...
}
[TestClass]
public class MyTests
{
// Creates a mock DbSet that can be used for Entity Framework contexts
private static Mock<DbSet<TEntity>> CreateMockDbSet<TEntity>(IEnumerable<TEntity> models) where TEntity : class
{
Mock<DbSet<TEntity>> dbSet = new Mock<DbSet<TEntity>>();
IQueryable<TEntity> queryable = models.AsQueryable();
dbSet.As<IQueryable<TEntity>>().Setup(e => e.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<TEntity>>().Setup(e => e.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<TEntity>>().Setup(e => e.GetEnumerator()).Returns(queryable.GetEnumerator());
dbSet.As<IQueryable<TEntity>>().Setup(e => e.Provider).Returns(queryable.Provider);
return dbSet;
}
// This test succeeds
[TestMethod]
public void GetOrders1()
{
Mock<DbSet<Customer>> customersDbSet = CreateMockDbSet(new List<Customer>
{
new Customer { CustomerId = "12345" }
});
Mock<DbSet<Order>> ordersDbSet = CreateMockDbSet(new List<Order>
{
new Order { OrderId = "0000000001", CustomerId = "12345" }
});
Mock<MyContext> context = new Mock<MyContext>();
context.Setup(e => e.Customers).Returns(customersDbSet.Object);
context.Setup(e => e.Orders).Returns(ordersDbSet.Object);
// This succeeds
List<Order> orders =
(from o in context.Object.Orders
select o).Include(p => p.Customer).ToList();
Assert.AreEqual(1, orders.Count);
}
// This test results in an exception that says "System.ArgumentNullException: Value cannot be null."
[TestMethod]
public void GetOrders2()
{
Mock<DbSet<Customer>> customersDbSet = CreateMockDbSet(new List<Customer>
{
new Customer { CustomerId = "12345" }
});
Mock<DbSet<Order>> ordersDbSet = CreateMockDbSet(new List<Order>
{
new Order { OrderId = "0000000001", CustomerId = "12345" }
});
Mock<MyContext> context = new Mock<MyContext>();
context.Setup(e => e.Customers).Returns(customersDbSet.Object);
context.Setup(e => e.Orders).Returns(ordersDbSet.Object);
// This fails
List<Order> orders =
(from o in context.Object.Orders.Include(p => p.Customer)
select o).ToList();
Assert.AreEqual(1, orders.Count);
}
}
The two tests are identical, except for the location of the Include
method in my LINQ query. I've checked this with a real database attached, and both ways of writing the query result in the same SQL being executed.
Why does the first test method succeed, but the second one results in an exception?