Testing – Autofixture con entidades inmutables
Estoy seguro que recordáis que en la última entrada estuvimos viendo como usar AutoFixture para construir datos de prueba en nuestros Tests, y seguro que recordáis que se cerraba la entrada comentando que si usábamos AutoFixture con entidades inmutables su comportamiento sería o no el mismo, que si lo usamos con entidades no inmutables, ya que el ejemplo que se veía en la anterior entrada es este último.
Bueno, pues en esta entrada vamos a ver su comportamiento, y como solventarlo.
Sigamos con los Tests de la entidad User, pero esta vez la entidad User va a ser inmutable.
public class User
{
public string Name { get; }
public string LastName { get; }
public string City { get; }
public string PhoneNumber { get; }
public string Gender { get; }
public string Email { get; }
public string MobileNumber { get; }
public int Age { get; }
private User(string name,
string lastName,
string city,
string phoneNumber,
string gender,
string email,
string mobileNumber,
int age)
{
Name = name;
LastName = lastName;
City = city;
PhoneNumber = phoneNumber;
Gender = gender;
Email = email;
MobileNumber = mobileNumber;
Age = age;
}
public static User Create(string name,
string lastName,
string city,
string phoneNumber,
string gender,
string email,
string mobileNumber,
int age)
{
return new User(name, lastName, city,
phoneNumber, gender, email, mobileNumber, age);
}
}
Lenguaje del código: JavaScript (javascript)
Ya hemos hecho a la entidad User inmutable, podéis ver como una vez que creemos un objeto de User ya no podremos modificar sus valores, por lo que nadie podrá alterar su valor una vez creada una instancia del objeto.
Bien, pues vamos a proceder a usar esta entidad en nuestros Tests, de la misma forma que lo hacíamos en la anterior entrada pero de momento sin customizar la edad, y vamos a ver el resultado:
public class UserShould
{
private Fixture fixture;
[SetUp]
public void Setup()
{
fixture = new Fixture();
}
[Test]
public void get_user_successfully_when_create_with_autofixture()
{
var user = fixture.Create<User>();
Assert.IsNotNull(user);
}
}
Lenguaje del código: PHP (php)
Aunque se que este Test no tiene mucho sentido, es solo de ejemplo para que se pueda ver el comportamiento de AutoFixture en este escenario.
El resultado de este Test es correcto, o sea, nos da verde, a pesar de ser una entidad inmutable ha creado la instancia del objeto perfectamente, sin ningún problema, y aquí dejo mis evidencias:

Hasta aquí todo está correcto ¿no?
¿Y si queremos customizar la edad ya que es el propósito del Test?
Bien, pues vamos a probarlo y a ver el resultado:
[Test]
public void get_message_under_eighteen_years()
{
var givenUserUnderEighteen = fixture.Build<User>()
.With(u => u.Age, 16)
.Create();
var message = home.Access(givenUserUnderEighteen);
Assert.IsTrue(string.Equals(message,
"Aplicación web solo para personas mayores de edad",
StringComparison.OrdinalIgnoreCase));
}
Lenguaje del código: JavaScript (javascript)
Efectivamente, tal y como os estabais imaginando no os va a dejar crear el objeto con la propiedad Age customizada, ya que esta es de solo lectura.

Por lo que no solo es que tengamos el Test en rojo, si no que estamos recibiendo un excepción no controlada en nuestros Tests, y esto no es correcto.
¿Y nos vamos a conformar con esto ? Yo no!!
Yo, que me acabo de enterar que las entidades hay que crearlas inmutables, he refactorizado todo mi código y he creado las entidades inmutables y ahora los Test me dan rojo, me saltan excepciones por todos los lados, con lo bonito que había dejado yo todo mi código refactorizado y ahora esto.
Buscando y buscando, buceando entre hilos e hilos de otros programadores buscando la misma solución al mismo problema, de repente.. !Eureka¡
!AutoFixture se puede customizar¡
Si, AutoFixture se puede customizar, y no es que lo diga yo, si no que lo comentan en StackOverflow, y si lo ahí lo dicen tiene que funcionar sí o sí, bueno o no, no sabemos, pero podemos probar.
La solución que se aporta de primeras es la de «mientras las propiedades sean readonly*, se puede definir un SpecimenBuilder personalizado para el tipo, y que se puede generalizar la customización para que funcione con cualquier propiedad».
La teoría es muy bonita, ¿pero esto funcionará?
Pues vamos a probarlo y salgamos de dudas. Customizamos de forma generalizada para trabajar con cualquier propiedad usando un Tipo T como genérico.
public class OverridePropertyBuilder<T, TProp> : ISpecimenBuilder
{
private readonly PropertyInfo _propertyInfo;
private readonly TProp _value;
public OverridePropertyBuilder(Expression<Func<T, TProp>> expr, TProp value)
{
_propertyInfo = (expr.Body as MemberExpression)?.Member as PropertyInfo ??
throw new InvalidOperationException("invalid property expression");
_value = value;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
return new NoSpecimen();
var camelCase = Regex.Replace(_propertyInfo.Name, @"(\w)(.*)",
m => m.Groups[1].Value.ToLower() + m.Groups[2]);
if (pi.ParameterType != typeof(TProp) || pi.Name != camelCase)
return new NoSpecimen();
return _value;
}
}
El código de arriba define una clase genérica OverridePropertyBuilder<T, TProp>
que implementa la interfaz ISpecimenBuilder
. Sobreescribe las propiedades de un objeto utilizado con AutoFixture.
Y posteriormente necesitamos métodos de extensión personalizados para facilitar su uso:
public class FixtureCustomization<T>
{
public Fixture Fixture { get; }
public FixtureCustomization(Fixture fixture)
{
Fixture = fixture;
}
public FixtureCustomization<T> With<TProp>(Expression<Func<T, TProp>> expr, TProp value)
{
Fixture.Customizations.Add(new OverridePropertyBuilder<T, TProp>(expr, value));
return this;
}
public T Create() => Fixture.Create<T>();
}
public static class CompositionExt
{
public static FixtureCustomization<T> For<T>(this Fixture fixture)
=> new FixtureCustomization<T>(fixture);
}
Lenguaje del código: PHP (php)
Y en la segunda se define una clase FixtureCustomization para ayudar a personalizar los fixture y una clase de extensión estática CompositionExt para proporcionar una forma conveniente de crear instancias FixtureCustomization.
Y de esta forma ya podremos crear objetos inmutables personalizados usando AutoFixture:
var givenUserUnderEighteen =
new Fixture()
.For<User>()
.With(x => x.Age, 16)
.Create();
Lenguaje del código: JavaScript (javascript)
Ahora ya podemos ver que con esta customización nos ha dejado crear el objeto sin ningún problema:

Pero la mejor forma de que entendáis este código es que lo probéis y lo depuréis. Os dejo mi ejemplo completo en GitHub para que le echéis un ojo.
¿ Que os ha parecido esta entrada? ¿ Cuál de todas las técnicas que hemos visto preferís ?