[英]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,並且我們的模擬方法實際上被調用了。
最終,當涉及到單元測試時,關鍵點是:
Times.None
)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.