EntityFramework.Testing icon indicating copy to clipboard operation
EntityFramework.Testing copied to clipboard

Suggested Enhancement: Add a second optional find method to better imitate DbSet.Remove

Open ConradPoohs opened this issue 11 years ago • 1 comments

I wanted to use the DbSet.Remove method in some repository tests, but found that the mock DbSet.Remove method was only doing reference comparison. So passing in an object with identical property values (including the primary key) to an existing item in the collection wouldn't cause it to be removed.

My fix was to update the SetupData extension method signature to add an optional "findEntity" parameter:

public static Mock<DbSet<TEntity>> SetupData<TEntity>(
  this Mock<DbSet<TEntity>> mock,
  ICollection<TEntity> data = null,
  Func<object[], TEntity> find = null,
  Func<TEntity, TEntity> findEntity = null
) where TEntity : class

And then modify the Remove/RemoveRange callbacks to call it:

mock.Setup(m => m.Remove(It.IsAny<TEntity>())).Callback<TEntity>(entity =>
{
    if (findEntity != null) entity = findEntity(entity);
    data.Remove(entity);
    mock.SetupData(data, find, findEntity);
});

mock.Setup(m => m.RemoveRange(It.IsAny<IEnumerable<TEntity>>())).Callback<IEnumerable<TEntity>>(entities =>
{
    foreach (var entity in entities)
    {
        if (findEntity != null) entity = findEntity(entity);
        data.Remove(entity);
    }

    mock.SetupData(data, find, findEntity);
});

I then setup my mock to compare primary keys instead of object references:

_mockDbSet.SetupData(_testData, 
  find: ids => _testData.Find(e => e.EntityId == (int)ids[0]),
  findEntity: en => _testData.FirstOrDefault(e => en != null && e.EntityId == en.EntityId)
);

Finally, I modified the Add/AddRange callbacks to retain the findEntity parameter:

mock..Setup(m => m.Add(It.IsAny<TEntity>())).Callback<TEntity>(entity =>
{
    data.Add(entity);
    mock.SetupData(data, find, findEntity);
});

mock.Setup(m => m.AddRange(It.IsAny<IEnumerable<TEntity>>())).Callback<IEnumerable<TEntity>>(entities =>
{
    foreach (var entity in entities)
    {
        data.Add(entity);
    };

    mock.SetupData(data, find, findEntity);
});

Not sure if there's a better way of doing this, but I think it's a helpful enhancement since DbSet.Remove() is often passed a deserialized object from the service layer rather than an object that was constructed by EF, so the expected behaviour is a primary key comparison, not an object reference comparison.

ConradPoohs avatar Apr 09 '15 14:04 ConradPoohs

Another option is to engage the PK detection logic from inside the EntityFramework

scott-xu avatar Apr 19 '15 06:04 scott-xu