Ja to robię w taki sposób.
- Interfejs IDbAccess - interfejs jest implementowany przez klasy, które obsługują konkretne połączenie z bazą danych (np. klasa używająca EfCore lub nHibernate, lub nawet czyste zapytania do bazy), np. tak może wyglądać:
public interface IDbAccess
{
event EventHandler ItemLoaded;
Task<T> GetAsync<T>(object id) where T : class;
Task<IList<T>> GetAsync<T>(QueryFilter<T> filters) where T : class;
Task<DbError> UpdateAsync<T>(T obj, object id) where T : class;
Task<DbError> SaveAsync<T>(T obj) where T : class;
Task<DbError> SaveOrUpdateAsync<T>(T obj) where T : class;
Task<DbError> DeleteAsync<T>(T obj) where T : class;
//IQueryable<T> Query<T>();
void BeginTransaction(IsolationLevel level = IsolationLevel.Unspecified);
DbError CommitTransaction();
void RollbackTransaction();
void SwitchToDb(string dbName);
Task<DbError> ReattachObject(object obj);
void CloseReattachedObject(object obj);
DbError GetLastDbError();
Task<long> GetCountAsync(string sql, object parameters);
Task<IEnumerable<T>> QueryAsync<T>(string sql, object parameters);
Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, object parameters);
}
Nie zwracaj uwagi na szczegóły. On jest robiony pod moje potrzeby, dlatego też możesz zobaczyć kilka dziwnych rzeczy, ale ogólnie tak może to wyglądać.
Następnie do serwisów jest wstrzykiwany obiekt implementujący ten interfejs:
class MyService
{
IDbAccess db;
public MyService(IDbAccess db)
{
this.db = db;
}
}
I później jest używany np. tak (to oczywiście uproszczone, bo dochodzi obsługa błędów itd):
public async Task<Client> GetClient(int id)
{
return await db.GetAsync<Client>(id);
}
A teraz jak wyglądają testy? Wystarczy moq:
Mock<IDbAccess> dbAccessMoq = new Mock<IDbAccess>();
dbAccessMoq.Setup(x => x.GetAsync(It.IsAny<int>())
.ReturnsAsync(null);
W tym momencie zawsze będzie zwracany null. Zamiast tego możesz zwrócić po prostu jakiegoś utworzonego klienta:
Mock<IDbAccess> dbAccessMoq = new Mock<IDbAccess>();
Client c = new Client();
dbAccessMoq.Setup(x => x.GetAsync(It.IsAny<int>())
.ReturnsAsync(c);
I tyle. Pamiętaj - nie testujesz bazy danych, tylko swój kod, który ma działać z bazą danych. Więc musisz się upewnić, co się stanie, gdy:
- IDbAccess wywali wyjątek
- zwróci nulla
- zwróci poprawne (oczekiwane) dane
- zwróci nieoczekiwane dane