Simular una dependencia que no hace nada es sencillo, pero ¿qué ocurre cuando esa dependencia devuelve un valor que afecta directamente la lógica del código? Aquí es donde la configuración de mocks con el método setup se vuelve indispensable para escribir pruebas unitarias verdaderamente independientes.
¿Por qué no basta con crear un mock sin configuración?
En un escenario anterior, al simular la dependencia de logger, bastaba con crear el mock sin configuración adicional. Esto funcionaba porque logger no devuelve ningún valor ni altera el flujo lógico del código [0:42]. Simplemente registra información internamente.
Sin embargo, cuando una dependencia retorna datos que el método necesita para continuar su ejecución, un mock vacío devuelve null por defecto. Esto rompe la prueba porque el resultado esperado nunca llega.
El ejemplo concreto es la función ReadFile dentro de la clase StringOperation [1:10]. Esta función:
- Recibe una interfaz
IFileReaderConnector como parámetro.
- Recibe el nombre del archivo a leer.
- Usa la interfaz para leer el archivo y devuelve el texto contenido.
A diferencia del logger, aquí la dependencia inyectada participa activamente en el valor de retorno.
¿Cómo configurar el comportamiento de un mock con setup?
El método setup de la librería Mock permite definir qué debe hacer una función simulada cuando es invocada [5:15]. La estructura es clara: se indica qué método se va a interceptar, qué parámetros espera recibir y qué valor debe retornar.
csharp
var mockFileReader = new Mock<IFileReaderConnector>();
mockFileReader
.Setup(fr => fr.ReadStream("file.txt"))
.Returns("reading file");
var strOperations = new StringOperation();
var result = strOperations.ReadFile(mockFileReader.Object, "file.txt");
Assert.Equal("reading file", result);
En este bloque ocurren varias cosas importantes:
- Se crea el mock del tipo
IFileReaderConnector [4:12].
- Se configura con
Setup para que cuando el método ReadStream reciba "file.txt", retorne "reading file" [5:40].
- Se pasa el objeto simulado como parámetro a
ReadFile.
- Se compara el resultado con el valor esperado.
La dependencia nunca lee un archivo real. Cree que está leyendo file.txt, pero en realidad devuelve exactamente lo que se configuró.
¿Qué sucede si el parámetro no coincide?
Si se cambia el nombre del archivo en la llamada, por ejemplo a "file2.txt", pero el setup sigue configurado para "file.txt", el mock devuelve null [7:05]. Es como si no existiera configuración alguna, porque la coincidencia de parámetros es estricta.
¿Cómo aceptar cualquier parámetro con It.IsAny?
Para escenarios donde no importa qué valor reciba la función, la librería Mock ofrece la clase It con el método IsAny<T>() [7:45]. Esto indica que sin importar el argumento, siempre se retornará el valor configurado.
csharp
mockFileReader
.Setup(fr => fr.ReadStream(It.IsAny<string>()))
.Returns("reading file");
Con esta configuración, da igual si se pasa "file.txt", "file2.txt" o cualquier otro string. El mock siempre devuelve "reading file" [8:20]. Esto resulta útil cuando la prueba se enfoca en validar la lógica posterior y no el parámetro específico de entrada.
¿Cuándo tiene sentido simular lógica compleja en dependencias?
En el ejemplo mostrado, ReadFile devuelve exactamente lo que retorna IFileReaderConnector. Pero en un escenario real, la dependencia podría realizar operaciones matemáticas, consultar servicios en la nube o procesar datos complejos [6:35]. Lo esencial es que las pruebas unitarias sean independientes de archivos físicos, servicios externos o cualquier recurso fuera del código bajo prueba.
Los puntos clave para recordar:
- Setup define el comportamiento esperado de un método dentro del mock.
- Returns especifica el valor que se devolverá al invocar ese método.
- It.IsAny permite flexibilizar los parámetros aceptados.
- Un mock sin configuración devuelve valores por defecto como
null.
Si has trabajado con dependencias más complejas en tus mocks, comparte cómo las configuraste y qué retos encontraste en el proceso.