Command
El CommandAttribute
es un atributo personalizado diseñado para simplificar la creación de pruebas unitarias que validen el comportamiento de los comandos en tu aplicación. Este atributo facilita la generación automática de instancias de comandos, lo que permite a los desarrolladores probar la creación de comandos de manera consistente y eficiente.
¿Cómo Funciona?
El CommandAttribute
funciona como un proveedor de datos para las pruebas unitarias usando xUnit. Cuando se aplica a un método de prueba, este atributo se encarga de:
- Escanear el ensamblado: Identifica todas las clases que implementan la interfaz
IRequest
, que es un marcador típico para comandos en arquitecturas CQRS. - Crear instancias: Para cada comando encontrado, se crea una instancia utilizando su constructor y valores predeterminados para sus parámetros.
- Proveer datos: Suministra la instancia del comando junto con su tipo y los valores de los parámetros utilizados en su creación como datos para la prueba unitaria.
Métodos
GetData
GetData(MethodInfo testMethod)
Este método es el corazón del CommandAttribute
. Se encarga de realizar las siguientes acciones:
- Localizar Comandos: Busca en el ensamblado especificado por el parámetro genérico
TAssemblyScan
todas las clases que implementan la interfazIRequest
. - Crear Instancias: Para cada comando encontrado:
- Obtiene el constructor del comando.
- 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 del comando utilizando
Activator.CreateInstance
y los valores de los parámetros.
- Retornar Datos: Retorna una secuencia de arrays de objetos, cada array contiene:
- El
Type
del comando. - La instancia del comando.
- Un diccionario con los
ParameterInfo
y los valores utilizados en la creación del comando.
- El
Implementación
El CommandAttribute
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 los comandos.
namespace CodeDesignPlus.Net.xUnit.Microservice.Attributes;
/// <summary>/// A custom attribute for providing data to test methods that validate commands./// </summary>/// <typeparam name="TAssemblyScan">The type of the assembly to scan for commands.</typeparam>[AttributeUsage(AttributeTargets.Method)]public class CommandAttribute<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 commands = typeof(TAssemblyScan).Assembly .GetTypes() .Where(x => !x.FullName.StartsWith("Castle") || !x.FullName.Contains("DynamicProxyGenAssembly")) .Where(x => typeof(IRequest).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract) .ToList();
foreach (var command in commands) { var constructor = command.GetConstructors().FirstOrDefault()!; var parameters = constructor.GetParameters(); var values = parameters.GetParameterValues(); var instance = Activator.CreateInstance(command, values.Values.ToArray());
yield return new object[] { command, instance, values }; } }}
Ejemplo de Uso
El siguiente ejemplo muestra cómo utilizar el CommandAttribute
en un método de prueba unitaria:
namespace CodeDesignPlus.Net.xUnit.Microservice.Test.Validations;
/// <summary>/// A class for validating commands./// </summary>public class CommandsTests{ /// <summary> /// Validates that commands can be created and their properties can be set and retrieved correctly. /// </summary> [Theory] [Command<Startup>] public void Commands_GetAndSet_Success(Type command, object instance, Dictionary<ParameterInfo, object> values) { // Assert Assert.NotNull(instance);
var properties = command.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:
[Command<Startup>]
crea instancias de todos los comandos en el ensamblado donde se defineStartup
, que implementanIRequest
.- El método de prueba recibe el
Type
del comando, lainstancia
del comando, y undiccionario
con los valores de los parámetros utilizados en la creación del mismo, lo que permite realizar aserciones sobre el objeto creado. - La prueba verifica que las propiedades del comando pueden ser obtenidas y que su valor es el esperado.
Conclusiones
El CommandAttribute
es una herramienta muy útil para simplificar la creación y validación de comandos en pruebas unitarias. Al automatizar la creación de instancias de comando y proporcionar los datos necesarios para las pruebas, este atributo ayuda a los desarrolladores a garantizar que su lógica de manejo de comandos se comporta según lo esperado.