Skip to content

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

@ConradPoohs

Description

@ConradPoohs

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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions