模仿 EF 核心 dbcontext 和 dbset

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

这是我的 DB 上下文,带有 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;
}
}
78742 次浏览

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

正如有人在评论中指出的那样,您应该使用内置在 内存提供程序中的 EF 进行测试。

我看到你在你的 MovieRepository中使用 EF 核心 DbContext。因此,使用 EFCoreInMemory数据库将是一个很好的选择,而不是使用 mock。这也将降低复杂性。

写你的 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(new 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核心程序包:

安装-包 Microsoft.EntityFrameworkCore.InMemory

详情请浏览: 使用 InMemory 进行测试

为了节省你的时间,尝试使用我的 Moq/NSubute 扩展 MockQueryable: < a href = “ https://github.com/romantitov/MockQueryable”rel = “ noReferrer”> 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版本支持的自动映射程序
  • 从1.1.0版本支持的 DbQuery
  • 从3.0版本支持 EF Core 3.0

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

构造 Moq (泛型方法)

克隆数据是为了允许测试并行运行,并防止测试访问被其他测试更改的数据。

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 的行为类似于通用存储库,而且模拟非常简单。

使用 实体框架核心包。

这很简单,就像:

using Moq.EntityFrameworkCore;


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

为单元测试项目(dot.net core 5和 xunit 2.4)设置你的依赖注入

1. add a startup.cs file with a class Startup
2.  public void ConfigureServices(IServiceCollection services)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.Development.json", false, true)
.Build();
//setups nlog dependency injection


services.AddControllers();


var connectionstring = configuration.GetConnectionString("DbCoreConnectionString");


services.AddDbContext<ViewpointContext>(options1 => options1.UseSqlServer(connectionString));


services.AddScoped<IRepositoryDB, RepositoryDB>();


services.ConfigureLoggerService();


}


3. in your xunit class add your dependency injection
IRepositoryDB _db;
public TestSuite(ITestOutputHelper output,IRepositoryDB db)
{
_db=db;
}