EventStore
El EventStoreContainer
es un componente de la librería CodeDesignPlus.Net.xUnit que proporciona un contenedor Docker para ejecutar EventStoreDB, un sistema de base de datos diseñado para almacenar secuencias de eventos. Este componente facilita la creación de pruebas unitarias que dependen de EventStoreDB, ofreciendo un entorno de pruebas aislado y reproducible.
¿Cómo Funciona?
El EventStoreContainer
utiliza Docker Compose
para definir y gestionar el contenedor EventStoreDB. Al inicializar una instancia de esta clase, se genera un archivo docker-compose.yml
temporal con la configuración necesaria para ejecutar el contenedor. Luego, el contenedor se inicia y se detiene automáticamente al final de la ejecución de las pruebas.
El proceso general de funcionamiento es el siguiente:
- Configuración del Contenedor: El método
Build()
define la configuración específica del contenedor EventStore, incluyendo el puerto interno, la ruta al archivodocker-compose.yml
, opciones de reinicio forzado, eliminación de contenedores huérfanos, parada al finalizar y un nombre de servicio alternativo generado dinámicamente para evitar conflictos. - Creación del Contenedor: Se crea una instancia de
DockerComposeCompositeService
usando la configuración definida. - Inicio y Detención del Contenedor: El contenedor se inicia cuando se crea una instancia de la clase
EventStoreContainer
y se detiene automáticamente cuando la instancia se elimina, asegurando la limpieza del entorno.
Container
El EventStoreContainer
hereda de la clase DockerCompose
, la cual proporciona la lógica base para interactuar con Docker Compose. La clase utiliza la clase DockerComposeCompositeService
de la librería Testcontainers
para iniciar y detener contenedores docker. La configuración del contenedor se define en el método Build()
que crea un archivo docker-compose.yml
con los parámetros necesarios para la ejecución del contenedor EventStore. El archivo docker-compose.yml
se encuentra dentro del directorio Containers/EventStoreContainer
.
namespace CodeDesignPlus.Net.xUnit.Containers.EventStoreContainer;
/// <summary>/// Represents a Docker container for EventStore, managed using Docker Compose./// </summary>public class EventStoreContainer : DockerCompose{ /// <summary> /// Builds the Docker Compose service configuration for the EventStore container. /// </summary> /// <returns>An <see cref="ICompositeService"/> representing the Docker Compose service.</returns> protected override ICompositeService Build() { // Set the internal port for the EventStore container. this.InternalPort = 1113;
// Define the path to the Docker Compose file. var file = Path.Combine(Directory.GetCurrentDirectory(), "Containers", "EventStoreContainer", (TemplateString)"docker-compose.yml");
// Configure the Docker Compose settings. var dockerCompose = new DockerComposeConfig { ComposeFilePath = new[] { file }, ForceRecreate = true, RemoveOrphans = true, StopOnDispose = true, AlternativeServiceName = "eventstore_" + Guid.NewGuid().ToString("N"), };
// Enable port retrieval and set the container name. this.EnableGetPort = true; this.ContainerName = $"{dockerCompose.AlternativeServiceName}-eventstore.db";
// Create and return the Docker Compose service. var compose = new DockerComposeCompositeService(base.DockerHost, dockerCompose);
return compose; }}
La clase EventStoreCollectionFixture
se utiliza como un fixture de xUnit, que proporciona una forma de compartir el contenedor entre las pruebas de una colección.
namespace CodeDesignPlus.Net.xUnit.Containers.EventStoreContainer;
/// <summary>/// Provides a fixture for managing an EventStore container during xUnit tests./// </summary>public sealed class EventStoreCollectionFixture : IDisposable{ /// <summary> /// The name of the collection for the EventStore tests. /// </summary> public const string Collection = "EventStore Collection";
/// <summary> /// Gets the EventStore container instance. /// </summary> public EventStoreContainer Container { get; }
/// <summary> /// Initializes a new instance of the <see cref="EventStoreCollectionFixture"/> class. /// </summary> public EventStoreCollectionFixture() { this.Container = new EventStoreContainer(); }
/// <summary> /// Disposes the EventStore container instance. /// </summary> public void Dispose() { this.Container.StopInstance(); }}
Ejemplo de Uso
Este ejemplo muestra cómo usar EventStoreContainer para realizar pruebas de integración con EventStoreDB. Se establece una conexión a una instancia de EventStoreDB, se escribe un nuevo evento a un stream, se lee este evento, y se verifica que su contenido coincida con lo escrito. La prueba demuestra la configuración de la conexión a EventStoreDB y cómo obtener la información de conexión desde EventStoreContainer.
using CodeDesignPlus.Net.xUnit.Containers.EventStoreContainer;using EventStore.ClientAPI;
namespace CodeDesignPlus.Net.xUnit.Test;
[Collection("EventStore Collection")]public class EventStoreContainerTest(EventStoreCollectionFixture eventStoreCollectionFixture){ private readonly EventStoreContainer eventStoreContainer = eventStoreCollectionFixture.Container;
[Fact] public async Task CheckConnectionServer() { var connectionSettings = ConnectionSettings.Create() .DisableTls() .UseConsoleLogger();
var connection = EventStoreConnection.Create(connectionSettings, new Uri($"tcp://localhost:{this.eventStoreContainer.Port}")); await connection.ConnectAsync();
// Escribir un evento var eventDataExptected = new EventData( eventId: Guid.NewGuid(), type: "MyEventType", isJson: true, data: Encoding.UTF8.GetBytes("{\"myProperty\":\"myValue\"}"), metadata: null );
await connection.AppendToStreamAsync("MyStream", ExpectedVersion.Any, eventDataExptected);
// Leer eventos var streamEvents = await connection.ReadStreamEventsForwardAsync("MyStream", start: 0, count: 10, resolveLinkTos: false);
foreach (var resolvedEvent in streamEvents.Events) { var eventData = resolvedEvent.Event; var eventPayload = Encoding.UTF8.GetString(eventData.Data);
Console.WriteLine($"Event Type: {eventData.EventType}"); Console.WriteLine($"Data: {eventPayload}");
Assert.Equal(eventDataExptected.Data, eventData.Data); }
connection.Close(); }}
using System;using CodeDesignPlus.Net.xUnit.Containers.EventStoreContainer;
namespace CodeDesignPlus.Net.xUnit.Test.Definitions;
[CollectionDefinition(EventStoreCollectionFixture.Collection)]public class EventStoreCollectionDefinition : ICollectionFixture<EventStoreCollectionFixture>{}
Conclusiones
- El
EventStoreContainer
facilita la creación de pruebas unitarias que requieren una instancia de EventStoreDB. - Utiliza Docker Compose para la gestión del contenedor, proporcionando una forma sencilla de definir la configuración.
- La clase
EventStoreCollectionFixture
permite compartir un contenedor entre todas las pruebas de una misma colección. - El uso del
EventStoreContainer
mejora la reproducibilidad y el aislamiento de las pruebas unitarias. - El contenedor EventStore se inicia automaticamente al comienzo de cada prueba y se detiene una vez termina, esto permite un ambiente de pruebas limpio.