在 Asp.net Core:Invalid 非虚拟设备上使用最小起订量问题(在 VB 中可覆盖)

Issue using Moq in Asp.net Core:Invalid setup on a non-virtual (overridable in VB)

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

Invalid setup on a non-virtual (overridable in VB) member: 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());
    }

这些是我的模型class:

  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,但它给出了另一个错误:

Can not instantiate proxy of class: EntitFrameworkCore.Models.Context

有什么问题?

确保 Context class 中的 Blogs 属性 可覆盖

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

您还应该考虑抽象化数据库上下文。

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

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

现在,如果 Context 属性是虚拟的,甚至都没有关系,因为您将在单元测试时模拟继承的接口。

您的 classes 也应该只依赖于抽象而不是实现细节。

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

错误信息已经很清楚了。

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

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

您真的不必嘲笑您的 DbContext。而是使用由内存数据库支持的 DbContext。请参阅 docs 了解如何设置上下文以使用内存提供程序,这是 InMemory 提供程序的主要目的。

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

var context = new AppDbContext(options);