María, parte de este equipo de trabajo, nos ha hecho el favor de escribir este artículo porque controla muy bien dicho tema.
Solución:
Este es nuestro enfoque:
-
En primer lugar, debe tener una abstracción encima de
IDbConnection
para poder burlarse de él:public interface IDatabaseConnectionFactory IDbConnection GetConnection();
-
Su repositorio obtendría la conexión de esta fábrica y ejecutaría el
Dapper
consulta sobre ello:public class ProductRepository private readonly IDatabaseConnectionFactory connectionFactory; public ProductRepository(IDatabaseConnectionFactory connectionFactory) this.connectionFactory = connectionFactory; public Task
> GetAll() return this.connectionFactory.GetConnection().QueryAsync ( "select * from Product"); -
Su prueba crearía una base de datos en memoria con algunas filas de muestra y verificaría cómo las recupera el repositorio:
[Test] public async Task QueryTest() // Arrange var products = new List
new Product ... , new Product ... ; var db = new InMemoryDatabase(); db.Insert(products); connectionFactoryMock.Setup(c => c.GetConnection()).Returns(db.OpenConnection()); // Act var result = await new ProductRepository(connectionFactoryMock.Object).GetAll(); // Assert result.ShouldBeEquivalentTo(products); -
Supongo que hay varias formas de implementar dicha base de datos en memoria; nosotros usamos
OrmLite
encima deSQLite
base de datos:public class InMemoryDatabase private readonly OrmLiteConnectionFactory dbFactory = new OrmLiteConnectionFactory(":memory:", SqliteOrmLiteDialectProvider.Instance); public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection(); public void Insert
(IEnumerable items) using (var db = this.OpenConnection()) db.CreateTableIfNotExists (); foreach (var item in items) db.Insert(item);
Adapté lo que hizo @Mikhail porque tuve problemas al agregar los paquetes de OrmLite.
internal class InMemoryDatabase
private readonly IDbConnection _connection;
public InMemoryDatabase()
_connection = new SQLiteConnection("Data Source=:memory:");
public IDbConnection OpenConnection()
if (_connection.State != ConnectionState.Open)
_connection.Open();
return _connection;
public void Insert(string tableName, IEnumerable items)
var con = OpenConnection();
con.CreateTableIfNotExists(tableName);
con.InsertAll(tableName, items);
he creado un DbColumnAttribute
para que podamos especificar un nombre de columna específico para una propiedad de clases.
public sealed class DbColumnAttribute : Attribute
public string Name get; set;
public DbColumnAttribute(string name)
Name = name;
Agregué algunas extensiones IDbConnection para el CreateTableIfNotExists
y InsertAll
métodos.
Esto es muy tosco, así que no he mapeado los tipos correctamente.
internal static class DbConnectionExtensions
public static void CreateTableIfNotExists(this IDbConnection connection, string tableName)
var columns = GetColumnsForType();
var fields = string.Join(", ", columns.Select(x => $"[x.Item1] TEXT"));
var sql = $"CREATE TABLE IF NOT EXISTS [tableName] (fields)";
ExecuteNonQuery(sql, connection);
public static void Insert(this IDbConnection connection, string tableName, T item)
BindingFlags.Instance)
.ToDictionary(x => x.Name, y => y.GetValue(item, null));
var fields = string.Join(", ", properties.Select(x => $"[x.Key]"));
var values = string.Join(", ", properties.Select(x => EnsureSqlSafe(x.Value)));
var sql = $"INSERT INTO [tableName] (fields) VALUES (values)";
ExecuteNonQuery(sql, connection);
public static void InsertAll(this IDbConnection connection, string tableName, IEnumerable items)
foreach (var item in items)
Insert(connection, tableName, item);
private static IEnumerable> GetColumnsForType()
BindingFlags.Instance)
let attribute = pinfo.GetCustomAttribute()
let columnName = attribute?.Name ?? pinfo.Name
select new Tuple(columnName, pinfo.PropertyType);
private static void ExecuteNonQuery(string commandText, IDbConnection connection)
using (var com = connection.CreateCommand())
com.CommandText = commandText;
com.ExecuteNonQuery();
private static string EnsureSqlSafe(object value)
return IsNumber(value)
? $"value"
: $"'value'";
private static bool IsNumber(object value)
var s = value as string ?? "";
// Make sure strings with padded 0's are not passed to the TryParse method.
if (s.Length > 1 && s.StartsWith("0"))
return false;
return long.TryParse(s, out long l);
Todavía puede usarlo de la misma manera que @Mikhail menciona en el Paso 3.