Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to Unit Test Generic Repository based on .NET EF Core fails when dealing with DbContext.Entry

I have written a generic repository (BaseRepository), where it's Delete method code is:

    public virtual void Delete(TEntity entity)
    {
        if (dbContext.Entry(entity).State == EntityState.Detached)
        {
            dbContext.Attach(entity);
        }
        dBContext.Remove(entity);        
    }

I want to unit-test the code. Since the DbContext is an external dependency, I just want to verify that when I will call the Repository.Delete(entity), then eventually the DbContext.Remove(entity) is to be called once. However, I have to Mock the behavior of dbContext.Entry... just before the actual dbContext.Remove(entity) call. The Unit Test code is:

    [Fact]
    public void Delete_SomeEntityToRepository_CallsTheAddMethod_To_DbContext()
    {
        // Arrange
        var testObject = new Customer()
        {
            Name = "Test-Customer"
        };
        
        var dbContextMock = new Mock<DbContext>();
        var dbEntityEntryMock = new Mock<EntityEntry<Customer>>();
        
        dbContextMock.Setup(d => d.Entry(testObject)).Returns(dbEntityEntryMock.Object);
        dbEntityEntryMock.Setup(e => e.State).Returns(EntityState.Unchanged);
        
        // Act
        var repository = new CustomerRepository(dbContextMock.Object);
        repository.Delete(testObject);

        //Assert
        dbContextMock.Verify(x => x.Remove(It.Is<Customer>(y => y == testObject)), Times.AtMost(1));
    }

However this code crashes, and actually at line: dbContextMock.Setup(d => d.Entry(testObject)).Returns(dbEntityEntryMock.Object); with message: Can not instantiate proxy of class: Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry`1[[Fx.CommonTests.DataAccess....

System.ArgumentException Can not instantiate proxy of class: Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry`1[[Fx.CommonTests.DataAccess.Customer, Fx.CommonTests, Version=4.3.0.0, Culture=neutral, PublicKeyToken=null]]. Could not find a parameterless constructor. (Parameter 'constructorArguments')...

Obviously the DbContext.Entry(...) is the problem. So, any ideas, about how to mock this???

I tried various variants of the code, as found in several articles, about how can I mock such cases, but always the end was the same. Any Ideas?

like image 725
Themis Zarotiadis Avatar asked Oct 24 '25 06:10

Themis Zarotiadis


1 Answers

This is how you can Mock dbcontext to prevent the test error, but I believe that there may be a different approach to consider when designing this test (see but section below):

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Moq;

// ...
[Fact]
public void Delete_SomeEntityToRepository_CallsTheAddMethod_To_DbContext()
{
    // Arrange
    var testObject = new Customer();

    var internalEntityEntry = GetInternalEntityEntry(testObject);

    var dbEntityEntryMock = new Mock<EntityEntry<Customer>>(internalEntityEntry);
    dbEntityEntryMock.Setup(e => e.State).Returns(EntityState.Unchanged);

    var dbContextMock = new Mock<DbContext>();
    dbContextMock.Setup(d => d.Entry(testObject)).Returns(dbEntityEntryMock.Object);

    // Act
    var repository = new CustomerRepository(dbContextMock.Object);
    repository.Delete(testObject);

    //Assert
    dbContextMock.Verify(x => x.Remove(It.Is<Customer>(y => y == testObject)), Times.AtMost(1));
}

private static InternalEntityEntry GetInternalEntityEntry(Customer testObject)
{
    return new InternalEntityEntry(
        new Mock<IStateManager>().Object,
        new RuntimeEntityType(
            name: nameof(Customer), type: typeof(Customer), sharedClrType: false, model: new(),
            baseType: null, discriminatorProperty: null, changeTrackingStrategy: ChangeTrackingStrategy.Snapshot,
            indexerPropertyInfo: null, propertyBag: false,
            discriminatorValue: null),
        testObject);
}

Full text here: https://gist.github.com/ctrl-alt-d/3d10384a06fa1e0c515e1f182fb83bb0

But (<- big "but"):

  • You shouldn't do it: https://github.com/dotnet/efcore/issues/27110
  • Read about how to write tests when you use EF https://learn.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking
  • If I were to write a test for your generic repository, this is how I would approach it: https://gist.github.com/ctrl-alt-d/8bc8d1f9e41a8ea98309397c46933fe4
like image 139
dani herrera Avatar answered Oct 26 '25 23:10

dani herrera



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!