在Asp.net Core中使用Moq的问题:非虚拟上的设置无效(在VB中可覆盖)

时间:2016-12-28 14:12:51

标签: c# unit-testing asp.net-core moq

我在asp.net核心中使用xunit和Moq编写单元测试,为此我遵循此Article,但我收到此错误:

  

非虚拟(VB中可覆盖)成员的设置无效:m => m.Blogs

这是我试过的:

 [Fact]
    public void CreateBlog()
    {
        var mockDbSet = new Mock<DbSet<Blog>>();
        var mockContext = new Mock<Context>();

        mockContext.Setup(m => m.Blogs).Returns(mockDbSet.Object); //In this line i got error

        var service = new BlogController(mockContext.Object);
        service.AddBlog("ADO.NET Blog", "adtn.com");

        mockDbSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
        mockContext.Verify(m => m.SaveChanges(), Times.Once());
    }

这些是我的模特课:

  public class Blog
  {
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public virtual IList<Post> Posts { get; set; }
  }

 public class Post
 {
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime RegisterDate { get; set; }
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; }
 }

这是我的控制者:

  public class BlogController : Controller
  {
    private readonly Context _context;
    public BlogController(Context ctx)
    {
        _context = ctx;
    }

    [HttpPost]
    public Blog AddBlog(string name, string url)
    {
        var blog = new Blog() { Name = name, Url = url };
        _context.Blogs.Add(blog);
        _context.SaveChanges();
        return blog;
    }
}

更新:我在上下文虚拟中创建dbset,但它给出了另一个错误:

  

无法实例化类的代理:EntitFrameworkCore.Models.Context

问题是什么?

2 个答案:

答案 0 :(得分:1)

确保Blogs类中的Context属性可以覆盖

public class Context : DbContext 
{ 
    public virtual DbSet<Blog> Blogs { get; set; } 
} 

您还应该考虑抽象db上下文。

public interface IContext {
    DbSet<Blog> Blogs { get; set; } 
}

public class Context : DbContext, IContext { 
    public virtual DbSet<Blog> Blogs { get; set; } 
} 

现在,如果Context属性是虚拟的,则无关紧要,因为在单元测试时您将模拟继承的接口。

您的课程也应仅依赖于抽象而非实施细节。

public class BlogController : Controller {
    private readonly IContext context;
    public BlogController(IContext context) {
        this.context = context;
    }
    //...other code
}

这样可以在隔离单元测试期间安全地进行模拟。

[Fact]
public void CreateBlog() {
    //Arrange
    var mockDbSet = new Mock<DbSet<Blog>>();
    var mockContext = new Mock<IContext>();

    mockContext.Setup(m => m.Blogs).Returns(mockDbSet.Object);

    var service = new BlogController(mockContext.Object);

    //Act
    service.AddBlog("ADO.NET Blog", "adtn.com");

    //Assert
    mockDbSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
    mockContext.Verify(m => m.SaveChanges(), Times.Once());
}

答案 1 :(得分:1)

错误信息已经非常明确了。

mockContext.Setup(m => m.Blogs)
    .Returns(mockDbSet.Object);

我认为Blogs是您的DbSet<Blog>属性,它不是虚拟的。模拟仅适用于标记为virtual的方法/属性或接口。

你真的不应该嘲笑你的DbContext。而是使用内存数据库支持的DbContext。有关如何设置上下文以使用内存提供程序的信息,请参阅docs,这是InMemory提供程序的主要目的。

var options = new DbContextOptionsBuilder<BloggingContext>()
    .UseInMemoryDatabase(databaseName: "sompe_database_name")
    .Options;

var context = new AppDbContext(options);