Skip to content

Query

El QueryAttribute es un atributo personalizado diseñado para simplificar la creación de pruebas unitarias que validen el comportamiento de las consultas (queries) en tu aplicación, especialmente en arquitecturas que utilizan patrones como CQRS. Este atributo automatiza la generación de instancias de consultas, facilitando la prueba de la correcta creación y el manejo de datos de las mismas.

¿Cómo Funciona?


El QueryAttribute funciona como un proveedor de datos para las pruebas unitarias utilizando xUnit. Cuando se aplica a un método de prueba, este atributo realiza las siguientes acciones:

  1. Escanear el ensamblado: Identifica todas las clases que implementan la interfaz genérica IRequest<T>, que es un marcador típico para las consultas en arquitecturas CQRS.
  2. Crear instancias: Para cada consulta encontrada, se crea una instancia utilizando su constructor y valores predeterminados para sus parámetros.
  3. Proveer datos: Suministra el tipo de la consulta, la instancia de la consulta, y un diccionario de los valores utilizados para crear la instancia a la prueba unitaria.

Métodos


GetData

GetData(MethodInfo testMethod)

Este método es el núcleo del QueryAttribute. Se encarga de realizar las siguientes acciones:

  • Localizar Consultas: Busca en el ensamblado especificado por el parámetro genérico TAssemblyScan todas las clases que cumplen con las siguientes condiciones:
    • Implementan la interfaz genérica IRequest<T>, donde T es el tipo de respuesta de la consulta.
    • No son una interfaz o clase abstracta.
  • Crear Instancias: Para cada consulta encontrada:
    • Obtiene el constructor de la consulta.
    • Obtiene los parámetros del constructor.
    • Genera un diccionario de valores predeterminados para los parámetros del constructor usando el método GetParameterValues().
    • Crea una instancia de la consulta utilizando Activator.CreateInstance y los valores de los parámetros.
  • Retornar Datos: Retorna una secuencia de arrays de objetos, cada array contiene:
    • El Type de la consulta.
    • La instancia de la consulta.
    • Un diccionario con los ParameterInfo y los valores utilizados en la creación de la consulta.

Implementación


El QueryAttribute se implementa como un atributo personalizado que hereda de DataAttribute de xUnit. Recibe un parámetro genérico TAssemblyScan que indica el ensamblado en el que buscar las consultas.

namespace CodeDesignPlus.Net.xUnit.Microservice.Attributes;
/// <summary>
/// A custom attribute for providing data to test methods that validate queries.
/// </summary>
/// <typeparam name="TAssemblyScan">The type of the assembly to scan for queries.</typeparam>
[AttributeUsage(AttributeTargets.Method)]
public class QueryAttribute<TAssemblyScan> : DataAttribute
{
/// <summary>
/// Gets the data for the test method.
/// </summary>
/// <param name="testMethod">The test method for which data is being provided.</param>
/// <returns>An enumerable of object arrays representing the data for the test method.</returns>
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
var queries = typeof(TAssemblyScan).Assembly
.GetTypes()
.Where(x => !x.FullName.StartsWith("Castle") || !x.FullName.Contains("DynamicProxyGenAssembly"))
.Where(x => x.GetInterfaces().ToList().Exists(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequest<>)) && !x.IsInterface && !x.IsAbstract)
.ToList();
foreach (var query in queries)
{
var constructor = query.GetConstructors().FirstOrDefault()!;
var parameters = constructor.GetParameters();
var values = parameters.GetParameterValues();
var instance = constructor.Invoke(values.Values.ToArray());
yield return new object[] { query, instance, values };
}
}
}

Ejemplo de Uso


El siguiente ejemplo muestra cómo utilizar el QueryAttribute en un método de prueba unitaria:

namespace CodeDesignPlus.Net.xUnit.Microservice.Test.Validations;
/// <summary>
/// A class for validating queries.
/// </summary>
public class QueriesTests
{
/// <summary>
/// Validates that queries can be created and their properties can be set and retrieved correctly.
/// </summary>
[Theory]
[Query<Startup>]
public void Queries_GetAndSet_Success(Type query, object instance, Dictionary<ParameterInfo, object> values)
{
// Assert
Assert.NotNull(instance);
var properties = query.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
var value = property.GetValue(instance);
var valueExpected = values.FirstOrDefault(x => x.Key.Name == property.Name).Value;
Assert.NotNull(value);
Assert.Equal(valueExpected, value);
}
}
}

En este ejemplo:

  • [Query<Startup>] crea instancias de todas las consultas en el ensamblado donde se define Startup que implementan IRequest<T>.
  • El método de prueba recibe el Type de la consulta, la instancia de la consulta, y un diccionario con los valores de los parámetros utilizados en la creación de la misma, lo que permite realizar aserciones sobre el objeto creado.
  • La prueba verifica que las propiedades de la consulta pueden ser obtenidas y que su valor es el esperado.

Conclusiones


El QueryAttribute es una herramienta valiosa para simplificar la creación y validación de consultas en pruebas unitarias. Al automatizar la creación de instancias de consulta y la provisión de datos necesarios para las pruebas, este atributo ayuda a los desarrolladores a garantizar que su lógica de consulta funciona como se espera.

Referencias Adicionales