DFord
DFord

Reputation: 2358

My Mocked DbContext writes to db in Unit Test using Moq

I am new to mocking and using Moq. This is my first time trying to Mock the add functionality. I have the mocks setup and it works for read functions, but when I try to unit test an add method, it saves the changes to the db.

How do I mock the add method?

I think I have to Mock my UnitOfWork class which has the SaveChanges() method. I want to know how I have to setup the mock to intercept the call to SaveChanges() and not save to db.

Here are the Mocks I have so far

[SetUp]
    public void SetUp()
    {
        addCount = 0;

        IEnumerable<Platform> platformList = new List<Platform>(){
            new Platform() { Id = 1, Name = "Unknown"},
            new Platform() { Id =2, Name = "Amazon"},
            new Platform() { Id = 3, Name = "Prime Pantry"}
        };
        var platformData = platformList.AsQueryable();

        var mockPlatformSet = new Mock<DbSet<Platform>>();
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.Provider).Returns(platformData.Provider);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.Expression).Returns(platformData.Expression);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.ElementType).Returns(platformData.ElementType);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.GetEnumerator()).Returns(platformData.GetEnumerator());
        mockPlatformSet.Setup(m => m.Add(It.IsAny<Platform>())).Callback(() => addCount++);

        var mockContext = new Mock<ApplicationDbContext>(){ CallBase = true };
        mockContext.Setup(m => m.Platforms).Returns(mockPlatformSet.Object);
        mockContext.Setup(m => m.Platforms.Add(It.IsAny<Platform>()));
        mockContext.Setup(m => m.Platforms.Add(It.IsAny<Platform>())).Callback(() => addCount++);


        unitOfWork = new UnitOfWork(mockContext.Object);
        platformRepo = new PlatformRepository(mockContext.Object);

        controller = new PlatformController(platformRepo, unitOfWork);
    }

Adding UnitOfWork code

public class UnitOfWork : IUnitOfWork 
{
    private readonly DbContext _context;
    private bool _isDisposed = false;

    public UnitOfWork(DbContext context)
    {
        _context = context;
    }

    public void SaveChanges()
    {
        _context.SaveChanges();
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_isDisposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _isDisposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

}

Updated question

I am trying to Unit Test my Create method of my PlatformController. In this method, I call the Add function on the repostiory then the SaveChanges function of UnitOfWork. I want to verify that my Platform object gets added to the DbSet, but 'intercept' the call to SaveChanges() to that it does not write to db.

How can I do this?

Upvotes: 3

Views: 6641

Answers (3)

DFord
DFord

Reputation: 2358

When I set the CallBase value of my mockContext as IUnitOfWork to false, that seems to have solved the problem of my unit test writing to the database.

This line of code: mockContext.As<IUnitOfWork>().CallBase = false;

Here is the code of my Setup function

[SetUp]
    public void SetUp()
    {
        addCount = 0;

        IEnumerable<Platform> platformList = new List<Platform>(){
            new Platform() { Id = 1, Name = "Unknown"},
            new Platform() { Id =2, Name = "Amazon"},
            new Platform() { Id = 3, Name = "Prime Pantry"}
        };
        var platformData = platformList.AsQueryable();

        var mockPlatformSet = new Mock<DbSet<Platform>>();
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.Provider).Returns(platformData.Provider);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.Expression).Returns(platformData.Expression);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.ElementType).Returns(platformData.ElementType);
        mockPlatformSet.As<IQueryable<Platform>>().Setup(m => m.GetEnumerator()).Returns(platformData.GetEnumerator());
        mockPlatformSet.Setup(m => m.Add(It.IsAny<Platform>())).Callback(() => addCount++);

        var mockContext = new Mock<ApplicationDbContext>(){ CallBase = true };
        mockContext.Setup(m => m.Platforms).Returns(mockPlatformSet.Object);
        mockContext.Setup(m => m.Platforms.Add(It.IsAny<Platform>()));
        mockContext.Setup(m => m.Platforms.Add(It.IsAny<Platform>())).Callback(() => addCount++);
        mockContext.Setup(m => m.Set<Platform>()).Returns(mockPlatformSet.Object);
        mockContext.As<IUnitOfWork>().CallBase = false;

        unitOfWork = new UnitOfWork(mockContext.Object);
        platformRepo = new PlatformRepository(mockContext.Object);

        controller = new PlatformController(platformRepo, unitOfWork);
    }

Upvotes: 5

C Bauer
C Bauer

Reputation: 5103

You're trying to do functional testing here, so it would be wise to have a functional database.

EF can recreate and destroy your database in your setup and teardown methods with a test connection string. This would provide a real functional testing environment for your tests to operate against mimicking the real environment.

Ex:

    [TestFixtureSetUp]
    public static void SetupFixture() //create database
    {
        using (var context = new XEntities())
        {
            context.Setup();
        }
    }

    [TestFixtureTearDown]
    public void TearDown() //drop database
    {
        using (var context = new XEntities())
        {
            context.Database.Delete();
        }
    }

    [SetUp]
    public void Setup() //Clear entities before each test so they are independent
    {
        using (var context = new XEntities())
        {
            foreach (var tableRow in context.Table)
            {
                context.Table.Remove(tableRow);
            }
            context.SaveChanges();
        }
    }

Change your connection string in your test project to point to "DbNameTest" and you're gold to use your XEntities class in your tests and it will be clear for setting up and adding test data to interact with.

Upvotes: -1

elolos
elolos

Reputation: 4450

I'm not sure this is the answer you were looking for, but abstracting the EF DbContext with a Unit of Work is a terrible idea. The reason is that the context already is a Unit of Work implementation. According to the description of the class in msdn:

Represents a combination of the Unit-Of-Work and Repository patterns and enables you to query a database and group together changes that will then be written back to the store as a unit.

Once you remove the unnecessary abstraction, mocking the context should be fairly easy, especially if you use the newest version of Entity Framework.

Upvotes: 3

Related Questions