Using Test Doubles with DbEntityEntry and DbPropertyEntry

2.5k Views Asked by At

I am using the new Test Doubles in EF6 as outlined here from MSDN . VS2013 with Moq & nUnit. All was good until I had to do something like this:

var myFoo = context.Foos.Find(id);

and then:

myFoo.Name = "Bar";

and then :

context.Entry(myFoo).Property("Name").IsModified = true;

At this point is where I get an error:

Additional information: Member 'IsModified' cannot be called for property 'Name' because the entity of type 'Foo' does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet.

Although, When I examine the 'Foos' in the context with an AddWatch I can see all items I Add'ed before running the test. So they are there.

I have created the FakeDbSet (or TestDbSet) from the article. I am putting each FakeDbSet in the FakeContext at the constructor where each one gets initialized. Like this:

Foos = new FakeDbSet<Foo>();

My question is, is it possible to work with the FakeDbSet and the FakeContext with the test doubles scenario in such a way to have access to DbEntityEntry and DBPropertyEntry from the test double? Thanks!

2

There are 2 best solutions below

1
On BEST ANSWER

I can see all items I Add'ed before running the test. So they are there.

Effectively, you've only added items to an ObservableCollection. The context.Entry method reaches much deeper than that. It requires a change tracker to be actively involved in adding, modifying and removing entities. If you want to mock this change tracker, the ObjectStateManager (ignoring the fact that it's not designed to be mocked at all), good luck! It's got over 4000 lines of code.

Frankly, I don't understand all these blogs and articles about mocking EF. Only the numerous differences between LINQ to objects and LINQ to entites should be enough to discourage it. These mock contexts and DbSets build an entirely new universe that's a source of bugs in itself. I've decided to do integrations test only when and wherever EF is involved in my code. A working end-to-end test gives me a solid feeling that things are OK. A unit test (faking EF) doesn't. (Others do, don't get me wrong).

But let's assume you'd still like to venture into mocking DbContext.Entry<T>. Too bad, impossible.

  • The method is not virtual
  • It returns a DbEntityEntry<T>, a class with an internal constructor, that is a wrapper around an InternalEntityEntry, which is an internal class. And, by the way, DbEntityEntry doesn't implement an interface.

So, to answer your question

is it possible to (...) have access to DbEntityEntry and DBPropertyEntry from the test double?

No, EF's mocking hooks are only very superficial, you'll never even come close to how EF really works.

0
On

Just abstract it. If you are working against an interface, when creating your own doubles, put the modified stuff in a seperate method. My interface and implementation (generated by EF, but I altered the template) look like this:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated from a template.
//
//     Manual changes to this file may cause unexpected behavior in your application.
//     Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Model
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;

    public interface IOmt
    {
        DbSet<DatabaseOmtObjectWhatever> DatabaseOmtObjectWhatever { get; set; }
        int SaveChanges();
        void SetModified(object entity);
        void SetAdded(object entity);
    }

    public partial class Omt : DbContext, IOmt
    {
        public Omt()
            : base("name=Omt")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

        public virtual DbSet<DatabaseOmtObjectWhatever> DatabaseOmtObjectWhatever { get; set; }

        public void SetModified(object entity)
        {
            Entry(entity).State = EntityState.Modified;
        }
        public void SetAdded(object entity)
        {
            Entry(entity).State = EntityState.Added;
        }
    }
}