JUnit Cómo parametrizar un test
Tags: java, testing, spring, spanish
Published: 2022-11-14 08:00:00

JUnit cuenta con una serie de funcionalidades orientadas a ejecutar tests en nuestra aplicación. 
Una de estas funcionalidades son los **tests parametrizados**, que nos permiten poder declarar los valores que necesitemos fuera del propio test.

Para entender su funcionamiento supongamos que nuestra aplicación cuenta con un **validador de códigos bancarios IBAN**, que comprueba si un valor determinado cumple el patrón esperado en dichos códigos.

*Los códigos IBAN utilizados en esta publicación han sido generados mediante la web http://randomiban.com/?country=Spain*

```java
class IbanValidator {
    // Regex pattern that an Iban must match
	static final String PATTERN = "^ES[0-9]{2}[0-9]{20}$";

    boolean isValid(String iban) {
        return iban.matches(PATTERN);
    }    
}
```

Ahora supongamos que tenemos un test sobre esta clase con la siguiente forma:

```java
@Test
@DisplayName(value = "Check validity of IBAN")
void check_validity_iban() {
    IbanValidator validator = new IbanValidator();
    assertTrue(validator.isValid("ES7921000813610123456789"));
}

// Test passed: 1 of 1 ✅
```

Como podemos ver, en este ejemplo estamos comprobando únicamente un valor concreto para el código IBAN, por lo que **el test estaría sesgado**.
Podríamos enfrentarnos a un **falso positivo**, ya que si desconocemos la implementación del método `isValid()` podría ser que siempre devuelva `true`.

Para resolver este problema, sería mejor probar la validación con diferentes valores de IBAN. Nuestro primer instinto podría ser copiar el test N veces, tantas como códigos IBAN diferentes queramos probar, pero JUnit nos facilita este trabajo mediante **el uso de la anotación** `@ParameterizedTest`.

## @ParameterizedTest

Para indicar el origen de los parámetros de un test parametrizado, debemos hacer uso de otras anotaciones como:

1. `@ValueSource`: permite indicar los valores directamente en la anotación.
2.  `@MethodSource`: permite hacer referencia a un método *proveedor* que contiene los parámetros.

### @ValueSource

Utilizando esta anotación, podemos declarar los valores del parámetro de nuestro test:

```java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@ParameterizedTest	// <- Utilizamos la anotación @ParameterizedTest en lugar de @Test
@ValueSource(strings = {"ES7921000813610123456789", "ES1600755981885574867892"}) // <- Indicamos en la anotación @ValueSource el tipo de parámetro (en este caso String) seguido de los valores que queremos probar
@DisplayName(value = "Check validity of IBAN")
void check_validity_iban(String iban) {	// <- Indicamos en el test el tipo y nombre del nuestro parámetro
    IbanValidator validator = new IbanValidator();
    assertTrue(validator.isValid(iban));
}

// Test passed: 2 of 2 ✅
```

Una de las limitaciones que tiene `@ValueSource` es que únicamente podemos hacer uso de un parámetro para el test, y en ocasiones esto puede ser insuficiente.
Para resolver este problema debemos hacer uso de la anotación alternativa  `@MethodSource`.

### @MethodSource

Aplicando esta anotación al mismo ejemplo, el test quedaría de la siguiente forma:

```java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

private static Stream<Arguments> testIbanValidityParams() {	// <- Creamos un método proveedor donde indicamos 
    return Stream.of(
        Arguments.of("ES7921000813610123456789"),
        Arguments.of("ES1600755981885574867892")
    )
}

@ParameterizedTest
@MethodSource(value = "testIbanValidityParams") // <- Indicamos en la anotación @MethodSource el nombre del método que contiene los parámetros
@DisplayName(value = "Check validity of IBAN")
void check_validity_iban(String iban) {
    IbanValidator validator = new IbanValidator();
    assertTrue(validator.isValid(iban));
}

// Test passed: 2 of 2 ✅
```

Como podemos ver, lo único que necesitamos es crear un método estático que devuelva un `Stream<Arguments>`, y estos serán utilizados por el test parametrizado.
Algunas de las ventajas de esta anotación frente a `@ValueSource` son:

1. Al extraer los parámetros a un método, podemos documentar mejor el test, ya que podemos comentar en el método proveedor información sobre cada parámetro.
2. El método `Arguments.of()` espera que **indiquemos 1 o N parámetros**, por lo que podemos aumentar los casos de uso de nuestro test realizando una simple modificación:

```java
/**
 * Parameters:
 * 1. String - An example of an IBAN code to be validated.
 * 2. boolean - The expected validation
 */
private static Stream<Arguments> testIbanValidityParams() {
    return Stream.of(
        Arguments.of("ES7921000813610123456789", true),	// <- Creamos un nuevo parámetro booleano con la respuesta esperada
        Arguments.of("ES1600755981885574867892", true),
        Arguments.of("ESX921000813610123456789", false),
        Arguments.of("E81600755981885574867892", false)
    )
}

@ParameterizedTest
@MethodSource(value = "testIbanValidityParams")
@DisplayName(value = "Check validity of IBAN")
void check_validity_iban(String iban, boolean expected) {	// <- Indicamos el nuevo parámetro 'expected'
    IbanValidator validator = new IbanValidator();
    assertEquals(actual, expected);	// <- Modificamos la afirmación de 'assertTrue' a 'assertEquals'
}

// Test passed: 4 of 4 ✅
```

En esta última versión del test se puede apreciar lo fácil que sería incluir un nuevo caso de uso para la validación, así como nuevos parámetros para comprobar en el test.