简体   繁体   中英

How to use Moq to unit test a delete operation in entity framework 6

*Update Edit - Partial Solution - Help still needed * - I found that the exception was just misleading. It was giving me this exception as I had got the number of times the mocked property was called wrong. It should have been called twice, instead of once. That part works now.

But I still do not understand why the entity is not being removed from the list. Is it because it is queryable?

Original Question Below

I have been trying to follow this link to learn how to unit Entity Framework 6 and 6.1.

However it does not show how to unit test a delete operation . Here is the code I am trying to test:

public void DeleteRequirement(int id)
{
    Requirement requirementToDelete = GetRequirement(id);
    context.Requirement.Remove(requirementToDelete);
    context.SaveChanges();
}

public Requirement GetRequirement(int id)
{
    return (from result in context.Requirement
            where result.Id == id
            select result).SingleOrDefault();
}

My unit test code is

[TestMethod]
public void DeleteRequirementSuccessfully()
{
    var requirements = new List<Requirement>
    {
        new Requirement {
            Id = 1,
            Title = "Requirement 1",
            Description = "Requirement 1 description"
        },
        new Requirement {
            Id = 2,
            Title = "Requirement 2",
            Description = "Requirement 2 description"
        },
        new Requirement {
            Id = 3,
            Title = "Requirement 3",
            Description = "Requirement 3 description"
        }
    }
    .AsQueryable();

    var mockDbSet = new Mock<DbSet<Requirement>>();
    var context = new Mock<RequirementsDatabaseEntities>();

    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.Provider)
             .Returns(requirements.Provider);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.ElementType)
             .Returns(requirements.ElementType);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.Expression)
             .Returns(requirements.Expression);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.GetEnumerator())
             .Returns(requirements.GetEnumerator());

    context.Setup(x => x.Requirement).Returns(mockDbSet.Object);

    var dataAccess = new RequirementsDataAccess(context.Object);
    int idToDelete = 1;
    dataAccess.DeleteRequirement(idToDelete);

    context.VerifyGet(x => x.Requirement, Times.Exactly(2)); // <- now verification is correct
    mockDbSet.Verify(x => x.Remove(It.IsAny<Requirement>()), Times.Once());
    context.Verify(x => x.SaveChanges(), Times.Once());
}

The test fails on the context.VerifyGet statement with the following error

Test method DataAccessTest.RequirementUnitTest+DeleteRequirement.DeleteRequirementSuccessfully threw exception:
System.InvalidOperationException: No connection string named
    'RequirementsDatabaseEntities' could be found in the application config file.

If I comment out the context.VerifyGet line the test passes, but the requirement is not deleted from the list. Does anyone have any idea why?

  1. The test fails
  2. And why when I comment out the offending line, it passes but the requirement has not been deleted.

Why isn't this working?

First edit your definition of requirements to be a List<Requirement> not a Queryable to be able to mocking add or remove. And use requirements.AsQueryable() in Setup methods.

Second add this code to mocking remove:

mockDbSet.Setup(m => m.Remove(It.IsAny<Requirement>())).Callback<Requirement>((entity) => requirements.Remove(entity));

So you can check the count of your requirements list after removing. Your code should be like this:

[TestMethod]
public void DeleteRequirementSuccessfully()
{
    var requirements = new List<Requirement>
    {
        new Requirement {
            Id = 1,
            Title = "Requirement 1",
            Description = "Requirement 1 description"
        },
        new Requirement {
            Id = 2,
            Title = "Requirement 2",
            Description = "Requirement 2 description"
        },
        new Requirement {
            Id = 3,
            Title = "Requirement 3",
            Description = "Requirement 3 description"
        }
    };

    var mockDbSet = new Mock<DbSet<Requirement>>();
    var context = new Mock<RequirementsDatabaseEntities>();

    // You should use .AsQueryable() in these lines
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.Provider)
             .Returns(requirements.AsQueryable().Provider);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.ElementType)
             .Returns(requirements.AsQueryable().ElementType);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.Expression)
             .Returns(requirements.AsQueryable().Expression);
    mockDbSet.As<IQueryable<Requirement>>()
             .Setup(x => x.GetEnumerator())
             .Returns(requirements.GetEnumerator());

    // This line should be added
    mockDbSet.Setup(m => m.Remove(It.IsAny<Requirement>())).Callback<Requirement>((entity) => requirements.Remove(entity));

    context.Setup(x => x.Requirement).Returns(mockDbSet.Object);

    var dataAccess = new RequirementsDataAccess(context.Object);
    int idToDelete = 1;
    dataAccess.DeleteRequirement(idToDelete);

    context.VerifyGet(x => x.Requirement, Times.Exactly(2));
    //mockDbSet.Verify(x => x.Remove(It.IsAny<Requirement>()), Times.Once());
    context.Verify(x => x.SaveChanges(), Times.Once());

    // add this Assert
    Assert.AreEqual(requirement.Count, 2);
    // or
    Assert.IsFalse(requirement.Any(x => x.Id == idToDelete));
}
  1. it fails because you can't mock non virtual method.
  2. the same problem: RequirementsDatabaseEntities.Requirement is not virtual method than it provide different output in test method than you expected. it probably returns empty collection.

fix: make RequirementsDatabaseEntities.Requirement getter virtual

Partial Solution - I found that the exception was just misleading. It was giving me this exception as I had got the number of times the mocked property was called wrong. It should have been called twice, instead of once. That part works now. But I still do not understand why the entity is not being removed from the list. Is it because it is queryable?

Since Moq uses inheritance in order to replace method calls you can only mock virtual methods (or interfaces).

So either make the methods/properties you're trying to fake virtual or use Isolator/JustMock etc. that work using Jit weaving and can fake these methods.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM