模拟EF核心dbcontext和dbset

Mar*_*sF8 8 c# unit-testing moq entity-framework-core asp.net-core

我正在使用ASP.NET Core 2.2,EF Core和MOQ。当我运行测试时,出现此错误:

消息:System.NotSupportedException:在非虚拟(在VB中可重写)成员上的无效设置:x => x.Movies

我做错了什么?

public class MovieRepositoryTest
{
    private readonly MovieRepository _sut;

    public MovieRepositoryTest()
    {
        var moviesMock = CreateDbSetMock(GetFakeListOfMovies());
        var mockDbContext = new Mock<MovieDbContext>();
        mockDbContext.Setup(x => x.Movies).Returns(moviesMock.Object);
        _sut = new MovieRepository(mockDbContext.Object);
    }

    [Fact]
    public void GetAll_WhenCalled_ReturnsAllItems()
    {
        //Act
        var items = _sut.GetAll();

        //Assert
        Assert.Equal(3, items.Count());
    }

    private IEnumerable<Movie> GetFakeListOfMovies()
    {
        var movies = new List<Movie>
        {
            new Movie {Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action"},
            new Movie {Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action"},
            new Movie {Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action"}
        };

        return movies;
    }

    private static Mock<DbSet<T>> CreateDbSetMock<T>(IEnumerable<T> elements) where T : class
    {
        var elementsAsQueryable = elements.AsQueryable();
        var dbSetMock = new Mock<DbSet<T>>();

        dbSetMock.As<IQueryable<T>>().Setup(m => m.Provider).Returns(elementsAsQueryable.Provider);
        dbSetMock.As<IQueryable<T>>().Setup(m => m.Expression).Returns(elementsAsQueryable.Expression);
        dbSetMock.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(elementsAsQueryable.ElementType);
        dbSetMock.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(elementsAsQueryable.GetEnumerator());

        return dbSetMock;
    }
  }
Run Code Online (Sandbox Code Playgroud)

这是我的数据库上下文,带有MoviedbSet:

public class MovieDbContext: DbContext
{
    public MovieDbContext(DbContextOptions<MovieDbContext> options) : base(options)
    {

    }

    public DbSet<Movie> Movies { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

并使用GetAll要测试的方法的存储库:

 public class MovieRepository: IMovieRepository
{
    private readonly MovieDbContext _moviesDbContext;
    public MovieRepository(MovieDbContext moviesDbContext)
    {
        _moviesDbContext = moviesDbContext;
    }

    public IEnumerable<Movie> GetAll()
    {
        return _moviesDbContext.Movies;
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 35

使用Moq.EntityFrameworkCore包。

这很简单:

using Moq.EntityFrameworkCore;

var myDbContextMock = new Mock<MyDbContext>();
var entities = new List<Entity>() { new Entity(), new Entity() };
myDbContextMock.Setup(x => x.Entities).ReturnsDbSet(entities);
Run Code Online (Sandbox Code Playgroud)

  • @DanRayson 只需使用该软件包的早期版本即可。 (3认同)

Tan*_*jel 20

我看你正在使用EF核心DbContext在你的MovieRepository。因此,代替使用模拟,使用EF Core InMemory数据库将是您的理想选择。这也将降低复杂度。

编写GetAllTest()方法如下:

[Fact]
public void GetAllTest()
{
        var options = new DbContextOptionsBuilder<MovieDbContext>()
            .UseInMemoryDatabase(databaseName: "MovieListDatabase")
            .Options;

        // Insert seed data into the database using one instance of the context
        using (var context = new MovieDbContext(options))
        {
            context.Movies.Add(new Movie {Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action"});
            context.Movies.Add(new Movie {Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action"});
            context.Movies.Add(nnew Movie {Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action"});
            context.SaveChanges();
        }

        // Use a clean instance of the context to run the test
        using (var context = new MovieDbContext(options))
        {
            MovieRepository movieRepository = new MovieRepository(context);
            List<Movies> movies == movieRepository.GetAll()

            Assert.Equal(3, movies.Count);
        }
}
Run Code Online (Sandbox Code Playgroud)

注意:不要忘记Microsoft.EntityFrameworkCore.InMemory按以下方式安装nuget软件包:

安装包Microsoft.EntityFrameworkCore.InMemory

有关更多详细信息:使用InMemory测试

  • 那不是单元测试。这就是集成测试。单元测试的重点是从单元测试中删除依赖项。因此,使用 ImMemory 数据库只是另一个依赖项。 (26认同)
  • @Bulgur 上面提到的链接是关于模拟 DBContext 的:“但是,我们从不尝试模拟 DbContext 或 IQueryable。这样做很困难、麻烦且脆弱。不要这样做。” 相反:“对使用 DbContext 的内容进行单元测试时,请使用 EF 内存数据库。” + 321X 指出了什么。 (14认同)
  • @MZawg 我们测试这部分是因为我们的服务依赖于 EF DbContext。实际上,单元测试应该模拟或存根所有依赖项。并使测试服务功能没有任何依赖性。单元测试的主要品质是速度。但是,如果您的服务具有 EF 依赖项(例如某些 DbContext 实现),那么您将问自己如何模拟或存根此依赖项。不错的选择是使用存储库模式,您可以在其中包装 DbContext。但这里我们讨论的是 EF 处理测试的内置能力。 (6认同)
  • @Bulgur 这不一定是真的。您可以使用它,但是:“只是不要这样做来测试实际的数据库查询或更新。” (5认同)
  • 根据 https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/#approach-3-the-ef-core-in-,EF Core 内存数据库不适合进行单元测试内存数据库 (3认同)

R.T*_*tov 20

为了节省您的时间,请尝试使用我的 Moq/NSubstitute 扩展 MockQueryable:https : //github.com/romantitov/MockQueryable 支持所有同步/异步操作

//1 - create a List<T> with test items
var users = new List<UserEntity>()
{
 new UserEntity,
 ...
};

//2 - build mock by extension
var mock = users.AsQueryable().BuildMock();

//3 - setup the mock as Queryable for Moq
_userRepository.Setup(x => x.GetQueryable()).Returns(mock.Object);

//3 - setup the mock as Queryable for NSubstitute
_userRepository.GetQueryable().Returns(mock);
Run Code Online (Sandbox Code Playgroud)

还支持 DbSet

//2 - build mock by extension
var mock = users.AsQueryable().BuildMockDbSet();

//3 - setup DbSet for Moq
var userRepository = new TestDbSetRepository(mock.Object);

//3 - setup DbSet for NSubstitute
var userRepository = new TestDbSetRepository(mock);
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 从 1.0.4 版本开始支持 AutoMapper
  • 从 1.1.0 版本开始支持 DbQuery
  • 从 3.0.0 版本开始支持 EF Core 3.0

  • @R.Titov所以我相信如果我正在测试通用存储库代码(这是一个内部库),那么我可以使用它而不必担心数据库的行为方式,因为我作为单元测试的目标是测试通用存储库代码而不是 DB 返回给我的东西!我对么? (2认同)