C# 在Asp.net Core中使用Moq的问题:在非虚拟服务器上的设置无效(在VB中可重写)

C# 在Asp.net Core中使用Moq的问题:在非虚拟服务器上的设置无效(在VB中可重写),c#,unit-testing,asp.net-core,moq,C#,Unit Testing,Asp.net Core,Moq,我在asp.net core中使用xunit和Moq编写了一个单元测试,为此我遵循了以下步骤,但出现了以下错误: 非虚拟(在VB中可重写)成员上的设置无效:m=>m.Blogs 这就是我所尝试的: [Fact] public void CreateBlog() { var mockDbSet = new Mock<DbSet<Blog>>(); var mockContext = new Mock<Context&g

我在asp.net core中使用xunit和Moq编写了一个单元测试,为此我遵循了以下步骤,但出现了以下错误:

非虚拟(在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 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的代理


问题出在哪里?

确保
上下文
类中的
博客
属性是可重写的

public class Context : DbContext 
{ 
    public virtual DbSet<Blog> Blogs { get; set; } 
} 
以便在隔离单元测试期间安全模拟

[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());
}
[事实]
public void CreateBlog(){
//安排
var mockDbSet=new Mock();
var mockContext=new Mock();
Setup(m=>m.Blogs).Returns(mockDbSet.Object);
var service=newblogcontroller(mockContext.Object);
//表演
AddBlog(“ADO.NET博客”、“adtn.com”);
//断言
mockDbSet.Verify(m=>m.Add(It.IsAny()),Times.Once());
验证(m=>m.SaveChanges(),Times.Once());
}

错误信息已经非常清楚了

mockContext.Setup(m => m.Blogs)
    .Returns(mockDbSet.Object);
我假设
Blogs
是您的
DbSet
属性,它不是虚拟的。mock只在标记为
virtual
的方法/属性或接口上工作

你不应该真的去模仿你的
DbContext
。而是使用由内存数据库支持的
DbContext
。请参见如何设置上下文以使用inmemory提供程序,这是inmemory提供程序的主要用途

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

var context = new AppDbContext(options);
var options=new DbContextOptionsBuilder()
.UseInMemoryDatabase(数据库名称:“sompe\u数据库名称”)
.选择;
var context=新的AppDbContext(选项);

错误消息会显示它;)我照文章说的做,所以文章出错了?上下文中的Blogs属性需要是虚拟的,你也可以抽象上下文。因为mock实际上正在创建一个仍在尝试连接到数据库的上下文实例,mock无法实例化它,因为DbContext没有无参数构造函数。请看下面的答案,并考虑使用iMouthyDB或使用dBraveTopStudioBu建器自己构建,但是你不能模仿它,必须使用真正的DB,这是不可进入的单元测试。这给了我另一个错误:
不能实例化类的代理:EntitFrameworkCore.Models.Context
你对此有什么想法吗?请不要尝试在接口中拆分上下文(我更喜欢其他形式的抽象),但是ASP.NET核心IoC是否能够在不使用显式的
services.AddScpüed(provider=>provider.GetService())的情况下注入它调用Startup.cs?@Tseng u更喜欢其他形式的抽象!你的意思是什么?@pejman:Repository,CQRS。但这两项都超出了本报告的范围question@Tseng您需要在启动时使用IoC容器对其进行配置
services.AddScoped(p=>newcontext(options))
最好使用内存数据库(并有效地将其转换为集成测试)。您的代码没有很好地分离/抽象,并且与持久性层紧密耦合,您的数据库关注点泄漏到应用程序的其他部分。这就是为什么你在itI上有问题的原因。我读了一些文章,其中使用了一个接口作为他们的上下文,我很喜欢他们,这是好事?这可能是一个有效的选择(见Nikosi的例子),尽管您仍然可能会遇到
IQueryable
IDbSet
泄漏到您的层中的问题,因为这允许在应用程序的任何部分修改查询(这可能会导致一些意外的连接和意外的低性能)。对于学习型应用程序或小型应用程序来说,这很好,但在企业级应用程序中可能会出现问题
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);
var options = new DbContextOptionsBuilder<BloggingContext>()
    .UseInMemoryDatabase(databaseName: "sompe_database_name")
    .Options;

var context = new AppDbContext(options);