简体   繁体   English

C# 实体框架最小起订量异常

[英]C# Entity Framework Moq exception

I have IDataService that contains generic crud operations我有包含通用 crud 操作的IDataService

public interface IDataService<T>
{
    Task<IEnumerable<T>> GetAll();
    Task<IEnumerable<T>> GetAll(string[] includes = null);
    Task<T> GetById(int id);
    Task<T> Create(T entity);
    Task<T> Update(int id, T entity);
    Task<bool> Delete(int id); 
}

and I have class GenericDataService<T> that implements the IDataService interface:我有 class GenericDataService<T>实现了IDataService接口:

public class GenericDataService<T> : IDataService<T> where T : DomainObject 
{
    private readonly DeployToolDBContexFactory _contexFactory;

    public GenericDataService(DeployToolDBContexFactory contexFactory)
    {
        _contexFactory = contexFactory;
    }

    public async Task<T> Create(T entity)
    {
        using (DeployToolDBContex contex = _contexFactory.CreateDbContext())
        {
            EntityEntry<T> createdResult = await contex.Set<T>().AddAsync(entity);
            await contex.SaveChangesAsync();
            return createdResult.Entity;
        }
    }

    public async Task<bool> Delete(int id)
    {
        using (DeployToolDBContex contex = _contexFactory.CreateDbContext())
        {
            T entity = await contex.Set<T>().FirstOrDefaultAsync((e) => e.Id == id);
            contex.Set<T>().Remove(entity);
            await contex.SaveChangesAsync();
            return true;
        }
    }
    
    public async Task<IEnumerable<T>> GetAll()
    {
        using (DeployToolDBContex contex = _contexFactory.CreateDbContext())
        {
            IEnumerable<T> entities = await contex.Set<T>().ToListAsync();
            return entities;
        }
    }
   
    public async Task<T> GetById(int id)
    {
        using (DeployToolDBContex contex = _contexFactory.CreateDbContext())
        {
            T entity = await contex.Set<T>().FirstOrDefaultAsync((e) => e.Id == id);
            return entity;
        }
    }
   
    public async Task<T> Update(int id, T entity)
    {
        using (DeployToolDBContex contex = _contexFactory.CreateDbContext())
        {
            entity.Id = id;
            contex.Set<T>().Update(entity);
            await contex.SaveChangesAsync();
            return entity;
        }
    }

    public async Task<IEnumerable<T>> GetAll(string[] includes = null)
    {
        using (DeployToolDBContex contex = _contexFactory.CreateDbContext())
        {
            var query = contex.Set<T>().AsQueryable();

            foreach (var include in includes)
                query = query.Include(include);

            return query.ToList();
        }
    }
}

To create objects I'm using a data store class that executes operations with the DataService object:为了创建对象,我使用数据存储 class 执行数据服务DataService的操作:

public class DataStore
{
    private static DataStore instance = null;

    public static DataStore Instance
    {
        get
        {
            instance = instance == null ? new DataStore() : instance;
            return instance;
        }
    }

    public IDataService<User> userDataService;

    public DataStore()
    {
        this.userDataService = new GenericDataService<User>(new DeployToolDBContexFactory());  
    }
}

For example, user creation:例如用户创建:

private async void AddUser()
{
    User user = new User()
                    {
                        UserName = UserNameTxt,
                        RoleId = RoleSelected.Id
                    };
    await DataStore.Instance.userDataService.Create(user);
}

I'm new to Moq and I want to write unit tests, for example, test of user creation我是 Moq 的新手,我想编写单元测试,例如用户创建测试

[Test]
public async Task CreateUser()
{
    Mock<DeployToolDBContexFactory> dbContextFactory = new Mock<DeployToolDBContexFactory>();
    Mock<DeployToolDBContex> dbContextMock = new Mock<DeployToolDBContex>(dbContextFactory.Object);

    var user = new User()
                   {
                       UserName = "testName"
                   };
    var mock = new Mock<GenericDataService<User>>(new DeployToolDBContexFactory());
    mock.Setup(m => m.Create(user)).ReturnsAsync(new User() { UserName = user.UserName}).Verifiable();
        var service = new GenericDataService<User>(dbContextFactory.Object);
        User u = await service.Create(user);
        Assert.AreEqual(u.UserName , user.UserName);
}

I'm getting this error:我收到此错误:

System.NotSupportedException: Unsupported expression: m => m.Create(user) System.NotSupportedException:不支持的表达式:m => m.Create(用户)
Non-overridable members (here: GenericDataService.Create) may not be used in setup / verification expressions.不可覆盖的成员(此处:GenericDataService.Create)不得在设置/验证表达式中使用。

I tried to set DbSet as virtual.我试图将 DbSet 设置为虚拟的。

Thanks for any help.谢谢你的帮助。

It looks like you are a bit mixed up with mocking and what exactly you should be testing.看起来您有点混淆了 mocking 以及您应该测试的内容。 Your GenericDataService is essentially a Generic Repository pattern.您的 GenericDataService 本质上是一个通用存储库模式。 This is actually an anti-pattern for EF, but there is plenty to read up on why you shouldn't use it with EF... Repository patterns in general are a good thing for facilitating unit testing, however this is because they serve as a boundary to avoid needing to try and mock a DbContext and its DbSets .这实际上是 EF 的反模式,但是关于为什么不应该将它与 EF 一起使用有很多内容需要阅读......一般来说,存储库模式对于促进单元测试一件好事,但这是因为它们用作避免需要尝试模拟DbContext及其DbSets的边界。

Firstly, your Test is testing the wrong thing.首先,您的测试正在测试错误的东西。 The test should be testing whatever method that will be calling your AddUser method.测试应该测试将调用您的 AddUser 方法的任何方法。 Whether this is in a Controller or a Service, etc. That controller would have a dependency of IDataService<User> declared which we would be mocking in order to test the controller:无论这是在 Controller 还是服务等中。controller 将具有IDataService<User>声明的依赖项,我们将是 mocking 以测试 controller:

For the sake of argument I've made AddUser() a public method.为了争论,我将 AddUser() 设为公共方法。 In your case you should have a public action or method that calls AddUser and would set up a test for that method.在您的情况下,您应该有一个公共操作或方法来调用 AddUser 并为该方法设置测试。 You should also structure your code to avoid being dependent on module-level state. For instance an AddUser method should not be reliant on private/protected state, it should ultimately be getting parameters to do things like perform actions or modify state. (kept to a minimum)您还应该构建代码以避免依赖于模块级 state。例如,AddUser 方法不应依赖于私有/受保护的 state,它最终应该获取参数来执行操作或修改 state。(保持最低)

So let's assume we want to test a method that should call the DataService Create method and Create is expected to have added the item to the DBContext and assigned an ID.因此,假设我们要测试一个应该调用 DataService Create 方法的方法,并且 Create 应该已将项目添加到 DBContext 并分配了一个 ID。 The purpose of the unit test is not to assert what EF actually does, but rather what the code under test should do with the results:单元测试的目的不是断言 EF 实际做了什么,而是被测代码应该如何处理结果:

[Test]
public void EnsureAddUserCreatesUser()
{
    const string userName = "New User";
    const int roleId = 4;
    const int UserId = 101;
    var mockUserDataService = new Mock<IDataService<User>>();
    mockUserDataService.Setup(m => m.Create(It.IsAny<User>())).Callback(m => {m.UserId = userId;});

    var testController = new UserController(mockUserDataService.Object);
    var user = await testController.AddUser(userName, roleId);
    Assert.IsNotNull(user);
    Assert.AreEqual(userId, user.UserId);
    Assert.AreEqual(userName, user.UserName);
    Assert.AreEqual(roleId, user.RoleId);
    mockUserDataService.Verify(m => m.Create(It.IsAny<User>()), Times.Once);        
}

What a test like this does is set up a mock of our repository/data service telling it to expect its input parameter.像这样的测试所做的是设置我们的存储库/数据服务的模拟,告诉它期望它的输入参数。 Since our AddUser method will be creating a new user based on some values, we tell it to expect It.IsAny<User>() which says "expect a user".由于我们的 AddUser 方法将基于某些值创建一个新用户,因此我们告诉它期待It.IsAny<User>() ,它表示“期待一个用户”。 From there we can have the mock perform some basic actions as if the DbContext added our user successfully such as populating a PK.从那里我们可以让模拟执行一些基本操作,就好像 DbContext 成功添加了我们的用户一样,例如填充 PK。 This example populates the PK using the Callback method which accepts whatever User was passed in then sets our known PK.此示例使用Callback方法填充 PK,该方法接受传入的任何用户,然后设置我们已知的 PK。 The value itself does not matter since we aren't actually inserting data, we just want a known value to get back that we can assert that the mock was actually called.这个值本身并不重要,因为我们实际上并没有插入数据,我们只想要一个已知值返回,我们可以断言模拟被实际调用了。 Normally the AddUser might also expect to check the resulting data and return something like a success state with an ID.通常,AddUser 可能还希望检查结果数据并返回带有 ID 的成功 state 之类的内容。 In this example I had the AddUser return the User.在此示例中,我让 AddUser 返回用户。 Other tests you might want to assert the behaviour if the AddUser attempts to add a duplicate user.如果 AddUser 尝试添加重复用户,您可能想要断言行为的其他测试。 In these cases the Moq might Throw an exception or otherwise return a different result.在这些情况下,Moq 可能会抛出异常或以其他方式返回不同的结果。

From there we have returned the User, so we just assert the values are what were expected, including the PK, and that our mocked method was actually called.从那里我们已经返回了用户,所以我们只是断言值是预期的,包括 PK,并且我们的模拟方法实际上被调用了。

Ultimately when it comes to unit testing, the key points are:最终,当涉及到单元测试时,关键点是:

  • Your DataService/Repository serves as the boundary for the tests.您的 DataService/Repository 充当测试的边界。 (What you mock) (你嘲笑什么)
  • Your tests test business logic above this boundary.您的测试测试此边界之上的业务逻辑。
  • Your tests mock the boundary, capturing all expected calls to that boundary, and either return known state, take action on passed in state, or throw exceptions based on what behaviour you want to test.您的测试模拟边界,捕获对该边界的所有预期调用,然后返回已知的 state,对传入的 state 采取行动,或者根据您要测试的行为抛出异常。
  • Your tests can then assert the mocks to verify that methods that were expected to be called were called, and any methods that were not expected to be called were not called.然后,您的测试可以断言模拟以验证是否调用了预期调用的方法,以及未调用任何预期调用的方法。 ( Times.None ) Times.None

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM