En este post voy a hablar de la librería fluent assertions, quizá muchos ya la conocéis y la usáis, aquí vamos a entrar en detalle sobre cómo utilizarla y por qué.
Índice
1 - Qué es Fluent Assertions?
Fluent Assertions es una librería de C# totalmente open source que vamos a utilizar para mejorar nuestros assertions dentro de los test unitarios, la forma en la que fluent assertions trabaja es con extension methods, lo que hace que las assertions se lean como una frase normal, lo que significa que son mas faciles de entender.
Como nota particular, si vienes de otro lenguaje de programación, seguramente te sea muy fácil de entender fluent assertions, ya que en otros lenguajes se utiliza este estilo por defecto.
2 - Ejemplo de Fluent assertions en C#
Primero de todo debemos incluir el paquete nuget FluentAssertions
, y ya está con esto ya podemos utilizar todo el potencial.
A la hora de elegir un framework para tests, yo normalmente prefiero xUnit
, pero, fluent Assertions funciona con todos, así que puedes elegir tu favorito.
Ahora lo que vamos a hacer es comparar cómo funcionan nuestros asserts normales diferenciados con fluent assertion.
- Nota: Todo este código está disponible en GitHub.
2.1 - Comprobación básica en fluent assertions
Para este ejemplo vamos a comprobar elementos básicos, lo más básico que solemos hacer en nuestros tests es comprobar propiedades, especialmente strings y booleanos
[Fact]
public void TestCode1()
{
string value1 = "Este es el valor de mi cadena de texto";
bool booleanValue = true;
Assert.Equal("Este es el valor de mi cadena de texto", value1);
Assert.True(booleanValue);
}
Y así sería la misma comprobación en Fluent Assertions.
[Fact]
public void TestCode1b()
{
string value1 = "Este es el valor de mi cadena de texto";
bool booleanValue = true;
value1.Should().NotBeNull();
value1.Should().Be("Este es el valor de mi cadena de texto");
booleanValue.Should().BeTrue();
}
Como puedes comprobar, estamos siempre utilizando el extension method Should()
, esto se hace por dos motivos, el primero, es para que se lea como inglés normal, el segundo para poder encadenar todos los extension methods de fluent assertions sin problema.
Posteriormente hemos visto el Be()
y NotBe()
los cuales son equivalentes a ==
y !=
; Pero esto es solo la punta del iceberg, vamos a ver en detalle, las diferentes opciones que tenemos.
2.2 - Fluent Assertions con string
Dentro de comparar strings podemos hacer muchas cosas, la básica, que es Be()
, ya la hemos visto, pero, que pasa si queremos comprobar, ignorando las mayúsculas y minúsculas? Entonces podemos utilizar BeEquivalentTo()
;
[Fact]
public void TestCode2()
{
string value1 = "Este es el valor de mi cadena de texto";
value1.Should().BeEquivalentTo("este es el valor de mi cadena de texto");
}
O por ejemplo BeLoweredCase()
comprueba que todo es minúscula, mientras que BeUpperCased()
comprueba que todo es mayúscula.
Otras comprobaciones muy importantes son BeEmpty()
o BeNull()
que obviamente comprueban si el string está vacío o es nulo. Y por supuesto, tenemos las negativas de todo lo que hemos visto hasta ahora.
NotBe()
, NotBeEquivalentTo()
, NotBeNull()
, NotBeNullOrEmpty()
, y NotBeUpperCased()
.
Finalmente, antes de terminar con las strings, podemos hacer match de una expresión regular con el método MatchRegex()
.
2.3 - Fluent assertions con otros tipos
No me voy a parar a explicar cada uno de los tipos de forma individual, ya que son todos más o menos similares, con Be..()
comprobamos si es igual, con NotBe…()
comprobamos si es diferente. Y como digo, aplica a todos los tipos, booleanos, enteros, datetime, etc.
Por ejemplo, podemos comprobar en el objeto DateTime
(o DateOnly
) si la fecha es anterior o posterior a cierto tiempo:
[Fact]
public void TestDates()
{
DateOnly dt = new DateOnly(2000,01,01);
dt.Should().BeBefore(DateOnly.FromDateTime(DateTime.UtcNow));
}
Al final no me puedo parar en cada elemento, porque para eso esta la documentación, pero si me voy a parar en puntos importantes.
2.4 - Fluent assertions con Colecciones
Donde si me voy a parar es en la comprobación de las colecciones. Y es que para mí, es donde radica el verdadero poder de la librería.
Una cosa que yo hago mucho es, cuando un método me devuelve una lista, compruebo el tamaño de la lista, y después cojo el primer elemento en una variable y comprobar sus propiedades clave.
- Nota: No compruebo todas, porque si hay un mapper o lo que sea, lo tengo testeado.
Si esta acción la hacemos con los assertions normales, queda bastante larga y fea:
private record ExampleObj(string Name, int Age);
[Fact]
public void TestCode3()
{
List<ExampleObj> list = new List<ExampleObj>()
{
new ExampleObj("Name1", 24),
new ExampleObj("Name2", 45),
new ExampleObj("Name3", 30),
};
Assert.True(list.Any());
ExampleObj firstElement = list.First();
Assert.Equal("Name1", firstElement.Name);
Assert.Equal(24, firstElement.Age);
}
Como vemos, estoy comprobando primero si la lista tiene algún elemento, y después cogiendo el primero para verificarlos;
Y es aquí donde triunfa FluentAssertions, en la complejidad del test viene la simplicidad de la librería, ya que esta misma acción se puede leer de una forma más natural.
[Fact]
public void TestCode3b()
{
List<ExampleObj> list = new List<ExampleObj>()
{
new ExampleObj("Name1", 24),
new ExampleObj("Name2", 45),
new ExampleObj("Name3", 30),
};
list.Should().NotBeNull()
.And.Contain(a => a.Name == "Name1")
.Which.Age.Should().Be(24);
}
Puede parecer más o menos difícil, o incluso chocante, sobre todo por la sintaxis, pero lo que está claro es, que si sabes inglés, entiendes, sin ninguna complicación lo que este código está haciendo.
Dentro de las colecciones podemos comprobar si están ordenadas por ciertos elementos o no, lo cual también es muy útil, esa opción la utilizo mucho, sobre todo cuando estoy testeando tema de paginación con filtro y sorting. BeInAscendingOrder y BeInDescendingOrder, por supuesto le podemos indicar por que propiedad queremos comprobar.
2.5 - Comprobación de excepciones con fluent assertions
Así como en el punto anterior FluentAssertions me parece superior y un motivo que puede llegar a justificar el uso de la librería, únicamente por como trata las colecciones. En el tema de las excepciones es el contrario.
La forma de comprobar excepciones en Xunit es bastante sencilla:
Assert.Throws<NotImplementedException>(() => ThisMethodReturnsException());
Ahora, en fluent assertions es un poco más compleja, tenemos que utilizar un delegado action, y de ahí comprobar la excepción:
[Fact]
public void TestCodeExceptionFA()
{
Action action = () => ThisMethodReturnsException();
action.Should().Throw<NotImplementedException>();
}
Por supuesto no es el fin del mundo, es un poco más largo, si pero el caso de uso de comprobar excepciones es mucho menor que el de cualquier otro tipo, pero ahí está.
Conclusión
En este post hemos visto una entrada a lo que es FluentAssertions, a porque muchos os lo encontráis en las empresas a las que vais, tiene características como todo lo relacionado con las colecciones que, en mi opinión, son superiores a la comprobación normal de los frameworks “por defecto”.
Además si vienes de otros lenguajes te puede resultar más sencillo, ya que la sintaxis es similar, y por supuesto facil de leer, ya que es inglés puro con muchas funciones que comprueban cosas “out of the box”. Como todas las librerías que son más o menos grandes, requieren curva de aprendizaje, y desde luego no está mal tener ciertos conocimientos sobre ella.