当在控制器内部调用时,Mocked DbSet方法抛出NotImplementedException

时间:2016-09-11 13:50:23

标签: c# asp.net-mvc entity-framework moq xunit.net

我有以下设置:

的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();
    }
}

型号:

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

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

控制器:

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");
    }
}

我使用Moq和xUnit编写单元测试来检查DeleteConfirmed功能:

 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());
        }
    }

测试在 DeleteConfirmed db.Album.Remove(album)中抛出以下异常:

  

System.NotImplementedException :成员“删除”尚未成功   在'DbSet 1Proxy' which inherits from 'DbSet 1'类型上实现。测试   'DbSet`1'的双精度必须提供方法和实现   使用的属性。

正如你在MockDbSet方法体中看到的那样,我为我的模拟设置了Remove方法,在单元测试中可以正常工作。你能解释一下为什么它在控制器内不起作用吗?

1 个答案:

答案 0 :(得分:1)

如果更改行,您的测试将正常运行:

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

要:

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

您为sutDbContext进行了设置,以便在调用mockAlbumSet.Object时返回sutDbContext.Album,但该行会覆盖您的设置,以便为sutDbContext.Album属性创建新的模拟对象并创建一个设置对于那个模拟:

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

这是一个简单的测试,它向您展示调用类的嵌套属性的设置,以前设置为返回Mock.Object,将使用新的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);
}