Saltar al contenido

Pruebas unitarias Dapper con consultas en línea

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:

  1. En primer lugar, debe tener una abstracción encima de IDbConnection para poder burlarse de él:

    public interface IDatabaseConnectionFactory
    
        IDbConnection GetConnection();
    
    
  2. 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");
        
    
    
  3. 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);
    
    
  4. Supongo que hay varias formas de implementar dicha base de datos en memoria; nosotros usamos OrmLite encima de SQLite 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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *