簡體   English   中英

C# 實體框架最小起訂量異常

[英]C# Entity Framework Moq exception

我有包含通用 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); 
}

我有 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();
        }
    }
}

為了創建對象,我使用數據存儲 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());  
    }
}

例如用戶創建:

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

我是 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);
}

我收到此錯誤:

System.NotSupportedException:不支持的表達式:m => m.Create(用戶)
不可覆蓋的成員(此處:GenericDataService.Create)不得在設置/驗證表達式中使用。

我試圖將 DbSet 設置為虛擬的。

謝謝你的幫助。

看起來您有點混淆了 mocking 以及您應該測試的內容。 您的 GenericDataService 本質上是一個通用存儲庫模式。 這實際上是 EF 的反模式,但是關於為什么不應該將它與 EF 一起使用有很多內容需要閱讀......一般來說,存儲庫模式對於促進單元測試一件好事,但這是因為它們用作避免需要嘗試模擬DbContext及其DbSets的邊界。

首先,您的測試正在測試錯誤的東西。 測試應該測試將調用您的 AddUser 方法的任何方法。 無論這是在 Controller 還是服務等中。controller 將具有IDataService<User>聲明的依賴項,我們將是 mocking 以測試 controller:

為了爭論,我將 AddUser() 設為公共方法。 在您的情況下,您應該有一個公共操作或方法來調用 AddUser 並為該方法設置測試。 您還應該構建代碼以避免依賴於模塊級 state。例如,AddUser 方法不應依賴於私有/受保護的 state,它最終應該獲取參數來執行操作或修改 state。(保持最低)

因此,假設我們要測試一個應該調用 DataService Create 方法的方法,並且 Create 應該已將項目添加到 DBContext 並分配了一個 ID。 單元測試的目的不是斷言 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);        
}

像這樣的測試所做的是設置我們的存儲庫/數據服務的模擬,告訴它期望它的輸入參數。 由於我們的 AddUser 方法將基於某些值創建一個新用戶,因此我們告訴它期待It.IsAny<User>() ,它表示“期待一個用戶”。 從那里我們可以讓模擬執行一些基本操作,就好像 DbContext 成功添加了我們的用戶一樣,例如填充 PK。 此示例使用Callback方法填充 PK,該方法接受傳入的任何用戶,然后設置我們已知的 PK。 這個值本身並不重要,因為我們實際上並沒有插入數據,我們只想要一個已知值返回,我們可以斷言模擬被實際調用了。 通常,AddUser 可能還希望檢查結果數據並返回帶有 ID 的成功 state 之類的內容。 在此示例中,我讓 AddUser 返回用戶。 如果 AddUser 嘗試添加重復用戶,您可能想要斷言行為的其他測試。 在這些情況下,Moq 可能會拋出異常或以其他方式返回不同的結果。

從那里我們已經返回了用戶,所以我們只是斷言值是預期的,包括 PK,並且我們的模擬方法實際上被調用了。

最終,當涉及到單元測試時,關鍵點是:

  • 您的 DataService/Repository 充當測試的邊界。 (你嘲笑什么)
  • 您的測試測試此邊界之上的業務邏輯。
  • 您的測試模擬邊界,捕獲對該邊界的所有預期調用,然后返回已知的 state,對傳入的 state 采取行動,或者根據您要測試的行為拋出異常。
  • 然后,您的測試可以斷言模擬以驗證是否調用了預期調用的方法,以及未調用任何預期調用的方法。 Times.None

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM