Domain Event
El DomainEventAttribute
es un atributo personalizado diseñado para simplificar la creación de pruebas unitarias que validen el comportamiento de los eventos de dominio en tu aplicación. Este atributo automatiza la generación de instancias de eventos de dominio, ya sea utilizando el constructor o un método estático Create
, lo que facilita la escritura de pruebas exhaustivas y reduce la repetición de código.
¿Cómo Funciona?
El DomainEventAttribute
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:
- Escanear el ensamblado: Identifica todas las clases que implementan la interfaz
IDomainEvent
, que es un marcador típico para eventos de dominio. - Crear instancias: Para cada evento de dominio encontrado, crea una instancia utilizando el constructor o el método estático
Create
(si existe). - Proveer datos: Suministra el tipo del evento de dominio, la instancia del evento 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 DomainEventAttribute
. Se encarga de realizar las siguientes acciones:
- Localizar Eventos de Dominio: Busca en el ensamblado especificado por el parámetro genérico
TAssemblyScan
todas las clases que cumplen con las siguientes condiciones:- Implementan la interfaz
IDomainEvent
. - No son una interfaz o clase abstracta.
- Implementan la interfaz
- Crear Instancias: Para cada evento de dominio encontrado:
- Método
Create
: Si el parámetrouseCreateMethod
estrue
, se busca un método estático llamadoCreate
en la clase del evento de dominio. Si no se encuentra, se lanza una excepción. - Constructor: Si el parámetro
useCreateMethod
esfalse
o no se encuentra el métodoCreate
, se utiliza el constructor por defecto del evento de dominio. - Genera un diccionario de valores predeterminados para los parámetros del constructor usando el método
GetParameterValues()
. - Crea una instancia del evento de dominio utilizando el método
Invoke
correspondiente (constructor o métodoCreate
) y los valores de los parámetros.
- Método
- Retornar Datos: Retorna una secuencia de arrays de objetos, cada array contiene:
- El
Type
del evento de dominio. - La instancia del evento de dominio.
- Un diccionario con los
ParameterInfo
y los valores utilizados en la creación del evento.
- El
Implementación
El DomainEventAttribute
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 eventos de dominio y un parámetro booleano useCreateMethod
que indica si debe utilizarse el método Create
o el constructor para crear la instancia del evento.
namespace CodeDesignPlus.Net.xUnit.Microservice.Attributes;
/// <summary>/// A custom attribute for providing data to test methods that validate domain events./// </summary>/// <typeparam name="TAssemblyScan">The type of the assembly to scan for domain events.</typeparam>/// <param name="useCreateMethod">Indicates whether to use the static Create method or the constructor to create instances of domain events.</param>[AttributeUsage(AttributeTargets.Method)]public class DomainEventAttribute<TAssemblyScan>(bool useCreateMethod) : 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 domainEvents = typeof(TAssemblyScan).Assembly .GetTypes() .Where(x => !x.FullName.StartsWith("Castle") || !x.FullName.Contains("DynamicProxyGenAssembly")) .Where(x => typeof(IDomainEvent).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract) .ToList();
foreach (var domainEvent in domainEvents) { object instance; Dictionary<ParameterInfo, object> values;
if (useCreateMethod) { var nameConstructor = domainEvent.GetMethod("Create", BindingFlags.Static | BindingFlags.Public);
if (nameConstructor is null) throw new CoreException($"The {domainEvent.Name} class does not have a static Create method.");
values = nameConstructor.GetParameters().GetParameterValues(); instance = nameConstructor.Invoke(null, [.. values.Values]); } else { var constructor = domainEvent.GetConstructors().FirstOrDefault()!; values = constructor.GetParameters().GetParameterValues(); instance = constructor.Invoke([.. values.Values]); }
yield return new object[] { domainEvent, instance, values }; } }}
Ejemplo de Uso
El siguiente ejemplo muestra cómo utilizar el DomainEventAttribute
en un método de prueba unitaria:
namespace CodeDesignPlus.Net.xUnit.Microservice.Test.Validations;
/// <summary>/// A class for validating domain events./// </summary>public class DomainEventTest{ /// <summary> /// Validates that domain events can be created using the constructor and their properties can be set and retrieved correctly. /// </summary> [Theory] [DomainEvent<Errors>(false)] public void DomainEvent_Constructor_ShouldSetAndRetrievePropertiesCorrectly(Type domainEvent, object instance, Dictionary<ParameterInfo, object> data) { // Assert Assert.NotNull(instance);
Assert.All(domainEvent.GetProperties(), property => { var value = property.GetValue(instance); var valueExpected = data.FirstOrDefault(x => x.Key.Name!.Equals(property.Name, StringComparison.OrdinalIgnoreCase)).Value; Assert.Equal(valueExpected, value); }); }
/// <summary> /// Validates that domain events can be created using the named constructor with custom values. /// </summary> [Theory] [DomainEvent<Errors>(true)] public void DomainEvent_CreateMethod_ShouldCreateInstanceWithCustomValues(Type domainEvent, object instance, Dictionary<ParameterInfo, object> values) { // Assert Assert.NotEmpty(values); Assert.NotNull(instance);
var property = domainEvent.GetProperty(nameof(DomainEvent.AggregateId)); var value = property!.GetValue(instance, null); var valueExpected = property.PropertyType.GetDefaultValue(); Assert.NotEqual(valueExpected, value); }}
En este ejemplo:
[DomainEvent<Errors>(false)]
crea instancias de todos los eventos de dominio en el ensamblado donde se defineErrors
utilizando el constructor.[DomainEvent<Errors>(true)]
crea instancias de todos los eventos de dominio en el ensamblado donde se defineErrors
utilizando el método estáticoCreate
, si existe.- Los métodos de prueba reciben el
Type
del evento de dominio, lainstancia
del evento de dominio 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 los valores de las propiedades del evento de dominio coincidan con los valores esperados.
- En el caso del metodo create verifica que se asigne un valor a la propiedad
AggregateId
que no es el valor por defecto.
Conclusiones
El DomainEventAttribute
es una herramienta muy útil para simplificar la creación y validación de eventos de dominio en pruebas unitarias. Al automatizar la creación de instancias y la provisión de datos de prueba, este atributo permite a los desarrolladores garantizar la calidad de su lógica de negocio relacionada con los eventos de dominio.