3

I have a method to get an Employer using an Entity Framework context (Lazy Loading is disabled). Sometimes I want the Employees included, sometimes I don't so I have the following code in my data access class:

public Employer GetEmployer(int employerId, bool includeRelationships)
{
    Employer employer;

    if (includeRelationships)
    {
        employer = (from e in this.context.Employers.Include(e => e.Employees)
                    where e.EmployerId == employerId
                    select e).SingleOrDefault();
    }
    else
    {
        employer = this.context.Employers.SingleOrDefault(e => e.EmployerId == employerId);
    }

    return employer;
}

From several questions about how to use NSubstitute to substitute EF context returns I have this extension method in my test project to hook up the DbSet calls for substitution (specifically NSubstitute DbSet / IQueryable<T>):

public static IDbSet<T> Initialise<T>(this IDbSet<T> dbSet, IQueryable<T> data) where T : class
{
    dbSet.Provider.Returns(data.Provider);
    dbSet.Expression.Returns(data.Expression);
    dbSet.ElementType.Returns(data.ElementType);
    dbSet.GetEnumerator().Returns(data.GetEnumerator());
    return dbSet;
}

This is then used to initialise a substitute set of Employers in the test class:

[TestInitialize]
public void TestInitialise()
{
    this.context = Substitute.For<EmployerContext>();

    this.dao = new EmployerDao(this.context);

    var employers = new List<Employer>();

    var employerWithoutEmployee = new Employer { EmployerId = 1 };

    employers.Add(employerWithoutEmployee);

    var employerWithEmployee = new Employer { EmployerId = 2 };

    var employee = new Employee { EmployeeId = 1, EmployerId = 2, Employer = employerWithEmployee };

    employerWithEmployee.Employees.Add(employee);

    employers.Add(employerWithEmployee);

    this.substituteEmployers = Substitute.For<IDbSet<Employer>>().Initialise(employers.AsQueryable());

    this.context.Employers.Returns(this.substituteEmployers);
}

So, I now have a test that looks like this:

[TestMethod]
public void ReturnsEmployerWithNullEmployeeWhenIncludeIsFalse()
{
    // Assemble
    var expectedEmployer = this.substituteEmployers.First(e => e.Employees.Any();

    var employerId = expectedEmployer.EmployerId;

    // Act
    var actualEmployer = this.dao.GetEmployer(employerId, false);

    var actualEmployee = actualEmployer.Employees.FirstOrDefault();

    // Assert
    Assert.AreSame(expectedEmployer, actualEmployer);
    Assert.IsNotNull(actualEmployer);
    Assert.IsNull(actualEmployee);
    this.context.Employers.ReceivedWithAnyArgs();
}

This test is failing on Assert.IsNull(actualEmployee);

In real usage, GetEmployer will return an Employer with no Employee children.

However, because I am substituting an Employer with Employee (because this is what I am testing!) the method is returning the substitute which has an Employee.

How can I test this?

Or, am I testing incorrectly?

Should I instead use the Employer which doesn't have an Employee, because that is what the context would return?

But then doesn't that make the test pointless!?!

I'm thinking myself in circles here...

Community
  • 1
  • 1
Shevek
  • 3,869
  • 5
  • 43
  • 63
  • Been a while since I used EF, but isn't this the default behavior? (i.e. virtual child collections are lazy-loaded automatically). – George Howarth Jun 20 '14 at 13:39
  • I've specifically switched off Lazy Loading... I'll update the question. – Shevek Jun 20 '14 at 13:45
  • It looks like you haven't set it in your `[TestInitialize]` method: `this.context.Configuration.LazyLoadingEnabled = false;` – George Howarth Jun 20 '14 at 13:51
  • Still fails when on the same line. When I debug the Employer has an Employee straight after the `employer = this.context.Employers.SingleOrDefault(e => e.EmployerId == employerId);` line in the dao method. its passing the substitute straight through – Shevek Jun 20 '14 at 13:55
  • Unless it's `employerWithEmployee.Employees.Add(employee);` that's messing it up. By doing that, it's setting `Employees` to a non-null value and thus mimicking the behavior of a context which has already lazily loaded that collection. Sorry, kind of clutching at straws here... – George Howarth Jun 20 '14 at 14:04
  • Yeah, that's exactly my thought. which is why I think that I'm testing the wrong thing possibly – Shevek Jun 20 '14 at 14:06
  • Now you can simply do: var set = Substitute.For, IQueryable, IDbAsyncEnumerable>() .SetupData(data); – Stephen Zeng Dec 04 '15 at 07:11

2 Answers2

3

Many times I have tried to mock out DbContext with different techniques. But every time when I thought "yeah, this time it behaves like real EF!" I have found yet another use-case when mock does not behave like a real thing. An having tests that pass with mocks does give you a false confidence. But when the same actions happen in production, you get exceptions and errors.

So my conclusion was to stop trying to mock DbContext and just do integration tests. It is a bit problematic to set up, but setting up realistic mock takes even more time! And I've written about how to have trouble-free integration tests in my blog: http://tech.trailmax.info/2014/03/how-we-do-database-integration-tests-with-entity-framework-migrations/

Now I tend to write a lot of integration tests (for CRUD stuff) that actually go into DB and mess about. Gives you much more assurance (rather than using db-mock) that the same actions will work in production.

Not really an answer to your question. Just my .02$

trailmax
  • 34,305
  • 22
  • 140
  • 234
0

This is not possible. It is a limitation of the EF "in-memory" testing model. See this article for details:

https://msdn.microsoft.com/en-us/data/dn314429#limitations

Stuart Clement
  • 541
  • 6
  • 14