Dobles de test

Si queremos tener bien testeado nuestro proyecto, hay test que deben estar aislados de conexiones de red o base de datos, para conseguir esto es necesario tener un código testable.

Un código testable se consigue cuando se hace uso de la inversión de dependencia en aquellas partes que queremos mantener aisladas. Así conseguimos que partes importantes no dependan de partes menos importantes y que van a cambiar con mayor frecuencia.

La parte más importante es el dominio o negocio y para mantenerlo aislado es necesario definir sus dependencias en modo de abstracciones.

Las dependencias reales del dominio extenderán o implementarán estas abstracciones consiguiendo así mantener el dominio desacoplado de sus dependencias.

Al escribir test vamos a poder crear objetos que también extienden o implementan de las abstracciones definidas en dominio y vamos a poder sustituir las dependencias reales por objetos que simulan ser los objetos reales.

Los objetos que simulan ser dependencias reales es lo que se conoce como dobles de test.

Tipos de dobles de test

Aunque popularmente se conoce a los dobles de test como mocks, esta definición no es correcta ya que este es solo uno de los tipos de dobles de test.

Vamos a revisar los diferentes tipos que existen:

Dummy

Es un tipo de objeto doble que no interviene en el test. El SUT (objeto bajo test) necesita que se le pase pero no interactúa con él durante el test.

public class DummyEmailValidator implements  
   EmailValidator {
      public boolean isValidEmail(String email) {
         return null;
   }
}

@Test
public void return_zero_user_count_when_no_exists_users() {

   givenThereAreNoUsers();

   DummyEmailValidator dummyEmailValidator = new DummyEmailValidator();

   UserService userService = new UserService(dummyEmailValidator);

   assertThat(userService.getUserCount(), is(0));
}
Stub

Es un tipo de objeto doble que si interviene en el test. Se configura una respuesta en el Stub para cuando le pregunte el SUT.

public class DummyEmailValidator implements  
   EmailValidator {
      public boolean isValidEmail(String email) {
         return false;
   }
}

@Rule 
public ExpectedException thrown = ExpectedException.none();

@Test
public void throw_exception_when_signup_with_invalid_email() {  
   thrown.expect(InvalidEmailException.class);

   StubEmailValidator stubEmailValidator = new StubEmailValidator();

   UserService userService = new UserService(stubEmailValidator);

   NewUserInfo userInfo = new NewUserInfo("name@");

   sut.singUp(userInfo);
}
Spy

Es un tipo de objeto doble que si interviene en el test. Se configura una respuesta en el Stub para cuando le pregunte el SUT y además registra si el sut se ha comunicado con él.

public class SpyEmailValidator implements  
   EmailValidator {
      Boolean isValidEmailWasCalled = false;

      public boolean isValidEmail(String email) {
         isValidEmailWasCalled = true;
         return false;
   }
}

@Rule 
public ExpectedException thrown = ExpectedException.none();

@Test
public void throw_exception_when_signup_with_invalid_email() {  
   thrown.expect(InvalidEmailException.class);

   SpyEmailValidator spyEmailValidator = new SpyEmailValidator();

   UserService userService = new UserService(spyEmailValidator);

   NewUserInfo userInfo = new NewUserInfo("name@");

   sut.singUp(userInfo);
}
Mock

Es un tipo de objeto doble que si interviene en el test. Se configura una respuesta en el Stub para cuando le pregunte el SUT y además participa en la verificación del test.

public class DummyEmailValidator implements  
   EmailValidator {
   Boolean isValidEmailWasCalled = false;

   public boolean isValidEmail(String email) {
      isValidEmailWasCalled = true;
      return false;
   }

   public boolean verify() {
      return isValidEmailWasCalled;
   }    
}

@Test
public void invoke_email_validator() {  
   MockEmailValidator mockEmailValidator = new MockEmailValidator();


   UserService userService = new UserService(mockEmailValidator);


   NewUserInfo userInfo = new NewUserInfo("name@email.com");


   userService.singUp(userInfo);

   mockEmailValidator.verify();
}
Fake

Es un tipo de objeto doble que si interviene en el test. Contiene cierta lógicay esta más cerca del ser la dependencia real.

Una base de datos en memoria es un buen ejemplo de este tipo de doble de test.

public class DummyEmailValidator implements  
   EmailValidator {

   public boolean isValidEmail(String email) {
      return email.toLowerCase().contains("@gmail.com");
   }    
}

@Test
public void add_user_when_signup_with_valid_email() {  
   FakeEmailValidator fakeEmailValidator = new FakeEmailValidator();


   UserService userService = new UserService(fakeEmailValidator);


   NewUserInfo userInfo = new NewUserInfo("name@email.com");


   userService.singUp(userInfo);

   User user = userService.getByEmail("xurxodev@gmail.com");

   assertThat(user, notNullValue());
}

Conclusiones

Si tenemos la necesidad de escribir test que sean rápidos y aislados de conexiones a red o base de datos, los dobles de test pueden ser un gran aliado.

Popularmente a los doble de test se les conoce como mocks pero como hemos visto es solo un tipo de doble de test.

Los dobles de test se pueden crear manualmente o mediante librerías específicas dentro de cada tecnología.