1

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();
}
K.Z
  • 5,201
  • 25
  • 104
  • 240

1 Answers1

0

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

Wojciech Rak
  • 540
  • 3
  • 17
  • Thanks, Wojciech. I have updated code as you describe and still getting following error .......... Expected invocation on the mock once, but was 0 times: m => m.Add(It.IsAny()) – K.Z Oct 17 '20 at 15:57
  • Tell me how you create `schoolDbContext` in `CreateSchool` class. If you inject it with constructor, in test you need to to call smth like this `new CreateSchool(schoolDbContextMock.Object) `. Give me link to repo if you can, or whole code so i can help you – Wojciech Rak Oct 17 '20 at 16:30
  • The DbContext 'SchoolDbContext' is created in Abstract class 'BaseCommand' . I have pasted the code in my question at the end – K.Z Oct 17 '20 at 16:37
  • I edited answer and give example for DBcontext injection. Here you can read more about creating DBContext object: https://learn.microsoft.com/pl-pl/ef/core/miscellaneous/configuring-dbcontext basically its about add one line more in Startup.cs `services.AddDbContext<>(your configuration);` – Wojciech Rak Oct 17 '20 at 16:46
  • Write if you have any problems – Wojciech Rak Oct 17 '20 at 20:27
  • I cannot pass SchoolDbContext in BaseCommand constructor like BaseCommand(IAppAmbientState ambientState, SchoolDbContext schoolDbContext) due to architecture reason. However I have created instance of SchoolDbContext directly inside CreateSchool and still getting same error. ... Expected invocation on the mock once, but was 0 times: m => m.Add(It.IsAny()) – K.Z Oct 18 '20 at 05:34
  • You must inject it, but you can make IFactory what makes DBContext, and mock this IFactory (that returns mockDBContext) instad of DBContext – Wojciech Rak Oct 18 '20 at 17:15
  • Did you achieve it? @Toxic – Wojciech Rak Oct 20 '20 at 16:56