Mocking ObjectStateEntry (from EF4)

6 posts, 0 answers
  1. Shane Milton
    Shane Milton avatar
    219 posts
    Member since:
    Dec 2003

    Posted 13 Aug 2010 Link to this post

    I'm attempting to mock an ObjectStateEntry from EF4. Unfortunately, it fails at runtime when I create the mocked object. This is my first time using JustMock (although I've done some very basic tests successfully) so I could have something setup/configured incorrectly. I'd appreciate some help getting this to work.

    var mockedEntry = Mock.Create<ObjectStateEntry>();

    This compiles just fine but has a runtime error:

    TestCase 'Foo'
    failed: System.TypeLoadException : Method 'set_EntityKey' in type 'ObjectStateEntry_Proxy_1f0c098e8cf2440f9eb3e6c8fa6abb46' from assembly 'Telerik.JustMock, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8b221631f7271365' does not have an implementation.
        at System.Reflection.Emit.TypeBuilder.TermCreateClass(RuntimeModule module, Int32 tk, ObjectHandleOnStack type)
        at System.Reflection.Emit.TypeBuilder.CreateTypeNoLock()
        at System.Reflection.Emit.TypeBuilder.CreateType()
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\DynamicProxy\TypeEmitter.cs(287,0): at ‚.‘.–()
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\DynamicProxy\Proxy.cs(87,0): at Telerik.JustMock.DynamicProxy.Proxy.–()
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\DynamicProxy\ProxyFactory.cs(78,0): at Telerik.JustMock.DynamicProxy.ProxyFactory.Create()
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\DynamicProxy\Fluent\FluentProxy.cs(72,0): at Telerik.JustMock.DynamicProxy.Fluent.FluentProxy.NewInstance()
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\DynamicProxy\Proxy.cs(81,0): at Telerik.JustMock.DynamicProxy.Proxy.Create(Type target, Action`1 action)
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\MockObject.cs(149,0): at ..(Type target, Container container, Object[] args)
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\MockObject.cs(123,0): at ..(Type target, Container container, Object[] args, Boolean profilerEnabled)
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\MockObject.cs(97,0): at ..(Type target, Behavior behavior, Boolean static, Object[] args)
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\MockObject.cs(44,0): at ..get_Instance()
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\Mock.cs(485,0): at Telerik.JustMock.Mock.Create(Type target, Behavior mode, Object[] args)
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\Mock.cs(427,0): at Telerik.JustMock.Mock.Create(Type target, Object[] args)
        c:\B\Basilisk\Basilisk CI Build\Sources\CodeBase\Telerik.JustMock\Mock.cs(416,0): at Telerik.JustMock.Mock.Create[T]()
        ExtensionMethods\ObjectStateEntryExtensionsTests.cs(39,0): at EG.Framework.EntityFramework.Tests.ExtensionMethods.ObjectStateEntryExtensionsTests.Does_UpdateAllEntityAuditFieldsAndCancelDeletes_Properly_Cancel_Deletes()
     
    0 passed, 1 failed, 0 skipped, took 1.00 seconds (NUnit 2.5.5).

    I'm working towards the following test:

    // Arrange
    var mockedEntry = Mock.Create<ObjectStateEntry>();
    var entity = new ObjectWithAuditFields();
     
    Mock.Arrange(() => mockedEntry.State).Returns(EntityState.Deleted);
    Mock.Arrange(() => mockedEntry.Entity).Returns(entity);
    Mock.Arrange(() => mockedEntry.EntityKey).IgnoreArguments();
    Mock.Arrange(() => mockedEntry.ChangeState(EntityState.Modified))
        .DoInstead(() => Mock.Arrange(() => mockedEntry.State).Returns(EntityState.Modified));
     
    // Act
    //mockedEntry.UpdateAllEntityAuditFieldsAndCancelDeletes(44);
    mockedEntry.ChangeState(EntityState.Modified);
     
    // Assert
    Assert.IsTrue(mockedEntry.State == EntityState.Modified);

    ... so that I can perform tests on the commented-out line. However, since I can't even create the mocked object, this is rather difficult...
  2. Ricky
    Admin
    Ricky avatar
    467 posts

    Posted 18 Aug 2010 Link to this post

    Hello Shane,

    Unfortunately, ObjectStateEntry cannot be mocked this way. It has some abstract internal members that can only be implemented internally, here in that case by the Entity Framework designer. Also, it is an abstract class therefore it is not possible to create the instance.

    When you create a ".edmx"  file, it internally implements the members from ObjectStateEntry and wraps it around a nice property named "ObjectStateManager" that can be accessed from the context class. For which it is possible to do the following:

    Customer customer = (from c in context.Customers                     
    where c.CustomerID == "ALFKI"                    
     select c).Single();
      
    ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer);
      
    Console.WriteLine("Customer object state: {0}", ose.State); /
      
    // Unchangedcustomer.Country = "UK";
      
    Console.WriteLine("Customer object state: {0}", ose.State);
      
    // Still Unchanged


    It basically let me track the state of the object generated by Entity Framework.

    Just out of curiosity, I also tried this with Moq (another popular mocking tool) and once I did the following it also crashed with some castle proxy error:

    var mock  = new Mock<ObjectStateEntry>();
    var fakeObject = mock.Object;


    Moving further, if you try to implement the class manually,  you will end up creating an error class.

    Finally,  you can check  more on change tracking with POCOs here (for further read):

    http://blogs.msdn.com/b/adonet/archive/2009/06/10/poco-in-the-entity-framework-part-3-change-tracking-with-poco.aspx?PageIndex=2

    Hope this information is helpful.

    Best wishes,
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  3. DevCraft R3 2016 release webinar banner
  4. Shane Milton
    Shane Milton avatar
    219 posts
    Member since:
    Dec 2003

    Posted 18 Aug 2010 Link to this post

    Thanks for the response. Given what you have told me, it sounds like JustMock is limited in a way that prevents me from actually testing the following code:


    public static void UpdateAllEntityAuditFieldsAndCancelDeletes(this ObjectStateEntry source, long currentUserSecurityID)
    {
        Debug.Assert(source != null);
        Debug.Assert(source.Entity != null);
        Debug.Assert(source.Entity is IEntity);
     
        var entity = source.Entity as IEntity;
     
        if (source.State == EntityState.Added)
        {
            entity.SetCreationAuditFields(currentUserSecurityID);
            entity.SetModifiedAuditFields(currentUserSecurityID);
        }
     
        if (source.State == EntityState.Modified)
        {
            entity.SetModifiedAuditFields(currentUserSecurityID);
        }
     
        if (source.State == EntityState.Deleted)
        {
            // Cancel the delete.
            source.ChangeState(EntityState.Modified);
            entity.SetRemovedAuditFields(currentUserSecurityID);
        }
    }

    What we are attempting to do here is make sure that whenever we have an ObjectStateEntry inserted/modified/removed, that our AuditFields get set. Additionally, whenever we delete something, we actually don't want to delete it - instead we want to do a soft-delete. However, I don't want this code all over the place. So we simply override the generated Context's SaveChanges(SaveOptions) method:

    public override int SaveChanges(SaveOptions options)
    {
        foreach (var entry in ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Deleted))
        {
            entry.UpdateAllEntityAuditFieldsAndCancelDeletes(33);
        }
     
        return base.SaveChanges(options);
    }

    We use STEs as our generated entities, but the problem here is not that we need to track their changes. That works perfectly. Our problem is that we want to override the the tracking of the object at the time of saving. And when doing so, we will conditionally change the state directly. Because we use POCO entities, they have no idea when they're being saved, only our Context does. So there is nothing we can do with our STEs directly to accomplish this. To accomplish this, we must utilize the ObjectStateEntry type. All of that code works perfectly but we simply don't have the ability to test it. I THOUGHT JustMock would have allowed us to test this (we actually use Moq for the majority of our mocking and it is also unable to mock this properly and this was the reason we decided to try using JustMock) since it's supposed to be able to mock abstract methods.

    Is JustMock simply incapable of mocking this object which means that there is no way for this functionality to be unit tested?
  5. Ricky
    Admin
    Ricky avatar
    467 posts

    Posted 19 Aug 2010 Link to this post

    Hello Shane,

    You should be able to test this code but you should use a different approach.

    The problem with the current approach is that JustMock can't mock ObjectEntryState. Actually not only JustMock but any of the mocking tools won't be able to create a mock for this class. It can't be mocked because it's an abstract class which contains abstract internal members and thus JustMock can't create a proxy class which inherits it.

    You could use the following approach to get the ObjectStateEntry instance (taking the customer example from the previous post):

    Customer customer = (from c in context.Customers                      
    where c.CustomerID == "ALFKI"                    
     select c).Single(); 
        
    ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer); 

    Once you  have the reference to ObjectStateEntry then you can directly setup your expectation inside the Mock.Arrange() call. 

    Hope that helps,
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  6. Shane Milton
    Shane Milton avatar
    219 posts
    Member since:
    Dec 2003

    Posted 19 Aug 2010 Link to this post

    Thanks for your reply. Unfortunately, I don't think that helps me. If I go with your recommendation, then I have to mock an ObjectContext in a way that serves out ObjectStateEntry objects. But as you've previously made apparent, I cannot mock ObjectStateEntries. This seems like it takes me back to the same problem as best as I can tell, and this still sounds like it's impossible to unit test this. So how would I properly mock an ObjectContext with JustMock for this purpose?

    On a side note, is there any documentation on things that JustMock cannot mock? There are quite a few claims such as "With JustMock, there are also no limits to what you can mock" but this is obviously not so. It sure would be nice for limitations to be easy to identify so we don't try to breach those limitations. We use a free mocking solution, Moq, because it handles most scenarios. If we hit limitations in that, then we expect that we might have to use a paid solution to get past those limitations. Since JustMock very heavily implies (and flat-out states) that it can mock without limitations, one would expect that JustMock could mock something like this. I'm not saying that you need to be able to (or even that it's possible), I'm just saying that if you can't, then don't claim that you can.

    -Shane
  7. Chris
    Admin
    Chris avatar
    221 posts

    Posted 20 Aug 2010 Link to this post

    Hi Shane,

    Sorry if I didn't express myself properly. You can mock ObjectStateEntry's members once you get the instance but you can't create a mock object out of it by using the Mock.Create method.
    e.g. you can still do:

    var entity = new ObjectWithAuditFields();
     
    Mock.Arrange(() => mockedEntry.State).Returns(EntityState.Deleted);
    Mock.Arrange(() => mockedEntry.Entity).Returns(entity);
    Mock.Arrange(() => mockedEntry.EntityKey).IgnoreArguments();
    Mock.Arrange(() => mockedEntry.ChangeState(EntityState.Modified))
        .DoInstead(() => Mock.Arrange(() => mockedEntry.State).Returns(EntityState.Modified));
     
    // Act
    mockedEntry.ChangeState(EntityState.Modified);
     
    // Assert
    Assert.IsTrue(mockedEntry.State == EntityState.Modified);

    but you can't do:

    var mockedEntry = Mock.Create<ObjectStateEntry>();

    and that's why we mentioned this workaround with getting the ObjectStateEntry from the ObjectStateManager and using the Mock.Arrange method afterwards.

    You're right that we've claimed that "with JustMock there are no limits what you can mock" but people should also have in mind that at the end we're stuck to the .NET CLR and limited to what it's possible there. Let me elaborate once more on the issue with mocking ObjectStateEntry.
    Generally we have a type like this in the System.Data.Entity assembly:

    public abstract class ObjectStateEntry
    {
        ...
        internal abstract void SomeMethod();
        ....
    }

    In order for JustMock to create a mock instance of this type, it should create dynamically a type which inherits it and implements its abstract members. In this specific case JustMock can't do it because this type has members which can be overriden only in types which reside in the System.Data.Entity assembly.
    Even theoretically, JustMock can't create a mock object for this type.
    I hate putting the blame on Microsoft but in this case... it's a design decision which makes your life (and our lives) harder. At first sight it looks like a public abstract type which should be absolutely easy to mock (even with the free mocking tools) but underneath it's locked.

    I hope this puts more light on the situation and thanks for the suggestion for describing better such limitations in the documentation. We'll definitely do that for the next release.

    Best wishes,
    Chris
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
Back to Top
DevCraft R3 2016 release webinar banner