模拟EF核心dbcontext和dbset

时间:2019-01-16 14:56:24

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

我正在使用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;
    }
  }

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

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

    }

    public DbSet<Movie> Movies { get; set; }
}

以及要测试的GetAll方法的存储库:

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

    public IEnumerable<Movie> GetAll()
    {
        return _moviesDbContext.Movies;
    }
}

4 个答案:

答案 0 :(得分:6)

我看到您在DbContext中使用EF核心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);
        }
}

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

  

安装软件包Microsoft.EntityFrameworkCore.InMemory

有关更多详细信息:Testing with InMemory

答案 1 :(得分:0)

收到的错误是因为您需要在dbcontext上将Movies属性声明为Virtual。

正如评论中指出的那样,您应该使用EF内置的memory provider进行测试。

答案 2 :(得分:0)

为节省时间,请尝试使用我的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);

还支持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);

注意:

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

答案 3 :(得分:0)

这是R.Titov在ASP.NET Core 3.1中完成的答案的开发:

构造最小起订量(通用方法)

克隆数据是为了允许测试并行运行,并防止测试访问另一个更改的数据。

public static Mock<DbSet<TEnt>> SetDbSetData<TEnt>(this Mock<IApplicationDbContext> dbMock,
        IList<TEnt> list, bool clone = true) 
    where TEnt : class
{
    var clonedList = clone ? list.DeepClone().ToList() : list.ToList();
    var mockDbSet = clonedList.AsQueryable().BuildMockDbSet();

    dbMock.Setup(m => m.Set<TEnt>()).Returns(mockDbSet.Object);
    dbMock.Setup(m => m.ReadSet<TEnt>()).Returns(mockDbSet.Object.AsQueryable());

    return mockDbSet;
}

使用一些测试数据

_appUserDbSetMock = _dbMock.SetDbSetData(ApplicationUserTestData.ApplicationUserData);

示例测试

[Fact]
private async Task Handle_ShouldAddANewUser()
{
    var command = new CreateApplicationUserCommand
    {
        // ...
    };

    await _handler.Handle(command, default);

    _appUserDbSetMock.Verify(m => m.AddAsync(It.IsAny<ApplicationUser>(), default), Times.Once);
}

使用MoqQueryable的一个优点是,由于DbSet的行为就像一个并且模拟非常简单,因此不需要通用存储库。