Perform xUnit Moq test to create record and save it to DbContext

764 Views Asked by At

I am writing test for Web API application written in .NET CORE 3.1. I am using xUnit, AutoFixture & Moq for testing. I have a class that creates a new school instance in the database using Entity Framework/ DbContext. My question is how to mock dbContext & save changes, further my School DataModel has one: many relationships with SchoolBranch DataModel. I have followed this tutorial https://learn.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking

Error

Message: 
Moq.MockException : 
Expected invocation on the mock once, but was 0 times: m => m.Add<School>(It.IsAny<School>())

Performed invocations:

   Mock<SchoolDbContext:1> (m):
   No invocations performed.

 Stack Trace: 
   Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
   Mock`1.Verify[TResult](Expression`1 expression, Times times)
   CreateSchoolCommandTest.ExecuteMethod_ShouldReturnNewGuidId_IfSuccess() line 50

School

 public class School
{
    public School()
    {
        this.SchoolBranches = new HashSet<SchoolBranch>();
    }

    public Guid SchoolID { get; set; }
    public string Name { get; set; }

    public ICollection<SchoolBranch> SchoolBranches { get; set; }
}

SchoolBranch

public class SchoolBranch
{
    public SchoolBranch()
    {
    }

    public Guid SchoolBranchID { get; set; }
    public Guid SchoolID { get; set; }
    public string Address { get; set; }
    public int PhoneNumber { get; set; }

    public School School { get; set; }
}

CreateSchool Class

public class CreateSchool : BaseCommand<Guid>, ICreateSchool
{
    public SchoolDto SchoolDtos { get; set; }

    public CreateSchool(IAppAmbientState appAmbient) : base(appAmbient) { }
   
    public override Guid Execute()
    {
        try
        {
            var schoolId = Guid.NewGuid();
            List<SchoolBranch> schoolBranches = new List<SchoolBranch>();

            foreach(var item in SchoolDtos.SchoolBranchDtos)
            {
                schoolBranches.Add(new SchoolBranch()
                {
                    SchoolBranchID = Guid.NewGuid(),
                    SchoolID = schoolId,
                    Address = item.Address,
                    PhoneNumber = item.PhoneNumber
                });
            }

            var school = new School()
             {
                 SchoolID = schoolId,
                 Name = SchoolDtos.Name,
                 SchoolBranches = schoolBranches
             };

            schoolDbContext.Schools.Add(school);
            schoolDbContext.SaveChanges();

            return school.SchoolID;

        }
        catch(Exception exp)
        {
            appAmbientState.Logger.LogError(exp);
            throw;
        }
    }
}

Test Class

 public class CreateSchoolCommandTest
 {
    private readonly ICreateSchool sut;
     private readonly Mock<IAppAmbientState> appAmbientState = new Mock<IAppAmbientState>();


 [Fact]
    public void ExecuteMethod_ShouldReturnNewGuidId_IfSuccess()
    {
        //Arrange
        var fixture = new Fixture();
        var schoolDtoMock = fixture.Create<SchoolDto>();

        var schoolDbSetMock = new Mock<DbSet<School>>();
        var schoolBranchDbSetMock = new Mock<DbSet<SchoolBranch>>();

        var schoolDbContextMock = new Mock<SchoolDbContext>();

        //schoolDbSetMock.Setup(x => x.Add(It.IsAny<School>())).Returns((School s) => s); // this also did not work
        

        schoolDbContextMock.Setup(m => m.Schools).Returns(schoolDbSetMock.Object);
        
        //Act
        sut.SchoolDtos = schoolDtoMock;
        var actualDataResult = sut.Execute();


        // Assert
        Assert.IsType<Guid>(actualDataResult);
        schoolDbContextMock.Verify(m => m.Add(It.IsAny<School>()), Times.Once());
        schoolDbContextMock.Verify(m => m.SaveChanges(), Times.Once());
    } 

BaseCommand (DbContext is created here)

public abstract class BaseCommand<T>
{
    protected SchoolDbContext schoolDbContext;
    protected IAppAmbientState appAmbientState { get; }

    public BaseCommand(IAppAmbientState ambientState)
    {
        this.schoolDbContext = new SchoolDbContext();
        this.appAmbientState = ambientState;
    }

    public abstract T Execute();
}
1

There are 1 best solutions below

8
Wojciech Rak On

For fix Error

You made just a little mistake. Insted of

schoolDbContextMock.Verify(m => m.Add(It.IsAny<School>()), Times.Once());
schoolDbContextMock.Verify(m => m.SaveChanges(), Times.Once());

You should have

schoolDbSetMock.Verify(m => m.Add(It.IsAny<School>()), Times.Once());
schoolDbContextMock.Verify(m => m.SaveChanges(), Times.Once());

Because you use method Add() on schoolDbContext.Schools not on schoolDbContext

For injecting dbContext

Your BaseCoommand class constructor should look like this:

public BaseCommand(IAppAmbientState ambientState, SchoolDbContext schoolDbContext)
{
    this.schoolDbContext = schoolDbContext;
    this.appAmbientState = ambientState;
}

Your CreateSchool class constructor:

public CreateSchool(IAppAmbientState appAmbient, SchoolDbContext schoolDbContext) : base(appAmbient, schoolDbContext) { }

And next in test you should initialize CreateSchool in test like this:

var sut = new CreateSchool(ambientState, schoolDbContextMock.Object);

And it will work