Caribou Caribou - 2 months ago 19
C# Question

Mocked DbSet method throws NotImplementedException when called inside of controller

I have the following setup:

DbContext:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public virtual DbSet<Album> Album { get; set; }

public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}

public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}


Model:

public class Album
{
public int AlbumID { get; set; }

[StringLength(150)]
public string Title { get; set; }
}


Controller:

public class AlbumController : Controller
{

ApplicationDbContext db = new ApplicationDbContext();

public AlbumController(ApplicationDbContext injectDb)
{
db = injectDb;
}

// POST: Albums/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Admin")]
public ActionResult DeleteConfirmed(int id)
{
Album album = db.Album.Find(id);

db.Album.Remove(album);
db.SaveChanges();
return RedirectToAction("Index");
}
}


I wrote the unit test using Moq and xUnit to check DeleteConfirmed functionality:

public class AlbumsControllerTests
{
public static Mock<DbSet<T>> MockDbSet<T>(List<T> inputDbSetContent) where T : class
{
var DbSetContent = inputDbSetContent.AsQueryable();
var dbSet = new Mock<DbSet<T>>();

dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(DbSetContent.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(DbSetContent.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(DbSetContent.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => inputDbSetContent.GetEnumerator());
dbSet.Setup(m => m.Add(It.IsAny<T>())).Callback<T>((s) => inputDbSetContent.Add(s));
dbSet.Setup(m => m.Remove(It.IsAny<T>())).Callback<T>((s) => inputDbSetContent.Remove(s));
return dbSet;
}

[Fact]
public void DeleteConfirmedTest()
{
// Arrange
var mockAlbumSet = MockDbSet(new List<Album> { });

Mock<ApplicationDbContext> sutDbContext = new Mock<ApplicationDbContext>() { CallBase = true };
sutDbContext.Setup(m => m.Album).Returns(mockAlbumSet.Object);

// Check if Album.Remove works inside this test
var albumToBeDeleted = new Album() { AlbumID = 1, Title = "TestAlbumName" };

sutDbContext.Object.Album.Add(albumToBeDeleted);
Assert.Equal(1, (from a in sutDbContext.Object.Album select a).Count());

sutDbContext.Object.Album.Remove(albumToBeDeleted);
Assert.Equal(0, (from a in sutDbContext.Object.Album select a).Count());

// Actual Test
sutDbContext.Object.Album.Add(albumToBeDeleted);
sutDbContext.Setup(m => m.Album.Find(It.IsAny<int>()))
.Returns(albumToBeDeleted);

AlbumController sut = new AlbumController(sutDbContext.Object);

var output = sut.DeleteConfirmed(1); // Throws NotImplementedException

// Assert
Assert.Equal(0, (from a in sutDbContext.Object.Album select a).Count());
}
}


The test throws the following exception on db.Album.Remove(album) in DeleteConfirmed:


System.NotImplementedException : The member 'Remove' has not been
implemented on type 'DbSet
1Proxy' which inherits from 'DbSet
1'. Test
doubles for 'DbSet`1' must provide implementations of methods and
properties that are used.


As you can see in MockDbSet method body, I setup Remove method for my Mock and it works just fine inside the unit test. Can you explain me why it doesn't work inside the controller?

Answer

Your test will work fine if you change your line:

sutDbContext.Setup(m => m.Album.Find(It.IsAny<int>()))
            .Returns(albumToBeDeleted);

To:

mockAlbumSet.Setup(x=>x.Find(It.IsAny<int>()))
           .Returns(albumToBeDeleted);

You made a setup for your sutDbContext to return mockAlbumSet.Object when sutDbContext.Album is called, but that line overridden your setup to create a new mock object for sutDbContext.Album property and created a single setup for that mock:

m.Album.Find(It.IsAny<int>()))
            .Returns(albumToBeDeleted);

Here is a simple test, that shows you that calling setup for nested property of the class, that was previously setup to return a Mock.Object, will override that property with a new Mock.Object:

public interface IParentService
{
    IDependantService Dependant { get; }
}

public interface IDependantService
{
    void Execute();
}

[Fact]
//This test passes
public void VerifyThatNestedMockSetupGeneratesNewMockObject()
{
    var value = 0;  

    var parentServiceMock = new Mock<IParentService>();
    var dependantServiceMock = new Mock<IDependantService>();
    dependantServiceMock.Setup(x => x.Execute()).Callback(() => { value = 1; });

    parentServiceMock.Setup(x => x.Dependant).Returns(dependantServiceMock.Object);

    Assert.Same(parentServiceMock.Object.Dependant, dependantServiceMock.Object);
    parentServiceMock.Setup(x => x.Dependant.Execute()).Callback(() => { value = 2; });
    Assert.NotSame(parentServiceMock.Object.Dependant, dependantServiceMock.Object);

    parentServiceMock.Object.Dependant.Execute();

    Assert.Equal(2, value);
}