@ ivan-stoev ответил на ваш вопрос о том, почему ваш оператор '.From Sql' ничего не делает, т.е. запрос никогда не материализуется. Но чтобы попытаться добавить немного дополнительной ценности, я поделюсь своей настройкой модульного теста, поскольку она мне подходит. Конечно, YMMV.
- Создайте повторно используемый класс для обработки общих c баз данных в памяти и простого заполнения таблиц тестовыми данными. NB: для этого требуются пакеты Nuget:
- ServiceStack.OrmLite.Core
- ServiceStack.OrmLite.Sqlite
Я использую OrmLite как он позволяет имитировать и модульное тестирование, предоставляя фабрику соединений без удаления, которую я могу аккуратно внедрить в классы Test через Dependency Injection:
/// <summary>
/// It is not possible to directly mock the Dapper commands i'm using to query the underlying database. There is a Nuget package called Moq.Dapper, but this approach doesnt need it.
/// It is not possible to mock In-Memory properties of a .NET Core DbContext such as the IDbConnection - i.e. the bit we actually want for Dapper queries.
/// for this reason, we need to use a different In-Memory database and load entities into it to query. Approach as per: https://mikhail.io/2016/02/unit-testing-dapper-repositories/
/// </summary>
public class TestInMemoryDatabase
{
private readonly OrmLiteConnectionFactory dbFactory =
new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider);
public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection();
public void Insert<T>(IEnumerable<T> items)
{
using (var db = this.OpenConnection())
{
db.CreateTableIfNotExist<T>();
foreach (var item in items)
{
db.Insert(item);
}
}
}
}
Класс '
DbConnectionManager<EFContext>
' для предоставления оболочки для соединения с базой данных с использованием контекста EF, который вы уже создали. Это захватывает соединение с базой данных из контекста EF и абстрагирует операции открытия / закрытия:
public class DbConnectionManager<TContext> : IDbConnectionManager<TContext>, IDisposable
where TContext : DbContext
{
private TContext _context;
public DbConnectionManager(TContext context)
{
_context = context;
}
public async Task<IDbConnection> GetDbConnectionFromContextAsync()
{
var dbConnection = _context.Database.GetDbConnection();
if (dbConnection.State.Equals(ConnectionState.Closed))
{
await dbConnection.OpenAsync();
}
return dbConnection;
}
public void Dispose()
{
var dbConnection = _context.Database.GetDbConnection();
if (dbConnection.State.Equals(ConnectionState.Open))
{
dbConnection.Close();
}
}
}
Сопутствующий вводимый интерфейс для вышеуказанного:
public interface IDbConnectionManager<TContext>
where TContext : DbContext
{
Task<IDbConnection> GetDbConnectionFromContextAsync();
void Dispose();
}
В вашем. NET классе запуска проекта зарегистрируйте этот интерфейс с помощью встроенного контейнера DI (или любого другого, который вы используете):
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IDbConnectionManager<>), typeof(DbConnectionManager<>));
}
Теперь наш класс Unit Test выглядит так:
/// <summary>
/// All tests to follow the naming convention: MethodName_StateUnderTest_ExpectedBehaviour
/// </summary>
[ExcludeFromCodeCoverage]
public class ProductTests
{
//private static Mock<ILoggerAdapter<Db2DbViewAccess>> _logger;
//private static Mock<IOptions<AppSettings>> _configuration;
private readonly Mock<IDbConnectionManager<Db2Context>> _dbConnection;
private readonly List<Product> _listProducts = new List<Product>
{
new Product
{
Id = 1,
Name = "Product1"
},
new Product
{
Id = 2,
Name = "Product2"
},
new Product
{
Id = 3,
Name = "Product3"
},
};
public ProductTests()
{
//_logger = new Mock<ILoggerAdapter<Db2DbViewAccess>>();
//_configuration = new Mock<IOptions<AppSettings>>();
_dbConnection = new Mock<IDbConnectionManager<Db2Context>>();
}
[Fact]
public async Task GetProductAsync_ResultsFound_ReturnListOfAllProducts()
{
// Arrange
// Using a SQL Lite in-memory database to test the DbContext.
var testInMemoryDatabase = new TestInMemoryDatabase();
testInMemoryDatabase.Insert(_listProducts);
_dbConnection.Setup(c => c.GetDbConnectionFromContextAsync())
.ReturnsAsync(testInMemoryDatabase.OpenConnection());
//_configuration.Setup(x => x.Value).Returns(appSettings);
var productAccess = new ProductAccess(_configuration.Object); //, _logger.Object, _dbConnection.Object);
// Act
var result = await productAccess.GetProductAsync("SELECT * FROM Product");
// Assert
result.Count.Should().Equals(_listProducts.Count);
}
}
Примечания к вышеизложенному:
- Вы можете видеть, что я тестирование класса доступа к данным «ProductAccess», который охватывает мои вызовы базы данных, но его должно быть достаточно легко изменить для вашей настройки. Мой класс ProductAccess ожидает, что будут введены другие службы, такие как ведение журнала и конфигурация, но я закомментировал их для этого минимального примера.
- Обратите внимание на настройку базы данных в памяти и заполнение ее списком тестов сущностей для запроса теперь представляет собой простые 2 строки (вы даже можете сделать это только один раз в конструкторе класса Test, если хотите, чтобы один и тот же набор тестовых данных использовался во всех тестах):
var testInMemoryDatabase = new TestInMemoryDatabase();
testInMemoryDatabase.Insert(_listProducts);