🌱 Digital Garden

Search

Search IconIcon to open search

Last updated Jul 20, 2023 Edit Source

Quizas ver primero: [[[N] Unit Testing con JUnit]]


# Mockito en Java

‘Mock’ se refiere a una tecnica utilizada para crear “mock objects”, los cuales son implementaciones de las dependencias que tiene una unidad que va a pasar por un Unit Test. Simula las dependencias y permite que se ejecute correctamente un proceso dependiente de una libreria, base de datos o de cualquier recurso externo.

Mockito es un framework de Java que implementa esta practica de crear mock objects. Es muy util y se puede usar en conjunto con JUnit para realizar muy buenos Unit Tests.

Para agregarlo debemos escribir su dependencia en nuestro gestor de dependencias (Gradle o Maven).

1
2
3
4
5
6
7
dependencies {
	// JUnit5
	testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
	// Mockito
	testImplementation 'org.mockito:mockito-core:3.11.2'
	testImplementation 'org.mockito:mockito-junit-jupiter:3.11.2'
}

# Usos y Problemas que Resuelven al hacer Unit Tests

Suponiendo que tuvieramos una clase como FxConverter, que convierte de una moneda a otra mediante su metodo .convert() y quisieramos probar su unidad quizas encontrariamos algunos problemas de dependencia, porque probablemente dentro de FxConverter tiene dependencias a otras clases como ‘RemoteFxRateService’, la cual a su vez podria tener sus propias dependencias, con lo cual enfrentariamos estos dos principales problemas.

  1. Bola de Nieve. Enfrentariamos una bola de nieve gigante con la cantidad de dependencias que podria tener una clase con otra hasta que pudiesemos llegar a probar nuestra unidad.
  2. Pruebas previas. Tomando en cuenta que quizas ya probamos las dependencias posteriores a nuestro FxConverter no tendria sentido instanciar todo y formar una bola de nieve, siendo que nosotros solo queremos hacer un test de la unidad FxConverter.

Para resolver estos problemas esque se crean los mock objects

# Crear Mock Objects usando Mockito

Hay dos formas principales para crear Mock Objects:

  1. Usando el metodo mock(Object.class)
  2. Usando anotaciones

Suponiendo que queremos escribir un test para nuestro FxConverter sin preocuparnos por su dependencia en RemoteFxRateService podriamos hacerlo de estas dos formas:

1
2
3
4
5
6
class FxConverterTest {

	//Mock object
	private RemoteFxRateService service = mock(RemoteFxRateService.class);
	private FxConverter converter = new FxConverter(service);
}

O si preferimos utilizar anotaciones podemos hacerlo como:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@ExtendWith(MockitExtension.class)
class FxConverterTest {

	//Mock object
	@Mock
	private RemoteFxRateService service;
	
	private FxConverter converter;

	@BeforeEach
	void setup() {
		converter = new FxConverter(service);
	}
}

En este caso tenemos dos nuevas anotaciones. @ExtendWith y @Mock. (Imagino que ExtendWith declara una clase la cual hara el Mock de objetos y @Mock marca los objetos a seguir este proceso)

# Definir comportamiento

Una vez que tenemos los mock objects creados, debemos definir su comportamiento cuando sus ciertos metodos sean llamados. Para definir esto podemos utilizar dos metodos estaticos: when() y thenReturn().

Siguiendo con nuestro ejemplo, probaremos la transformacion entre dos monedas iguales y dos diferentes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class FxConverterTest {

	//Mock object
	private RemoteFxRateService service = mock(RemoteFxRateService.class);
	private FxConverter converter = new FxConverter(service);

	@Test
	@DisplayName("Given 100 USD convert to USD. Return 100.00")
	void convertSameCurrency() {
		when(service.getRate("USD", "USD")).thenReturn("1.00");

		BigDecimal result = converter.convert("USD", "USD", "100.00");
		assertEquals("100", result.toString());
	}

	@Test
	@DisplayName("Given 100 USD convert to EUR. Return 84.97")
	void convertDifferentCurrency() {
		when(service.getRate("USD", "EUR")).thenReturn("0.8497");

		BigDecimal result = converter.convert("USD", "EUR", "100.00");
		assertEquals("84.97", result.toString());
	}
}

Those two tests will pass, if we use another currency thats not defined inside the when() and thenReturn() methods we will fail because the behavior is not defined. If a method behavior is not defined Mockito returns a default value:

# Argument Matchers

Adicionalmente, podemos especificar Argument Matchers para que Mockito se encargue de la busqueda y ejecucion de estos argumentos particulares, de modo que no tengamos que especificarlo todo nosotros mismos.

Debemos tener en cuenta que, si utilizamos un Argument Matcher todos los argumentos de nuestros metodos deben ser Matchers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void test3() {
	// Correcto ambos son matchers
	when(mockObject.someMethod(anyString(), anyString())).thenReturn("Valid Strings");
	
	// Incorrecto "hola" no es un matcher
	when(mockObject.someMethod(anyString(), "hola")).thenReturn("Valid Strings");

	// Correcto, eq es un matcher
	when(mockObject.someMethod(anyString(), eq("hola"))).thenReturn("Valid Strings");
}

Para ver todos los matchers disponibles podemos leer la Documentacion oficial de Mockito. Sin embargo, entre los mas usados tenemos a: any(), anyString(), anyInt(), eq(value).

# Throwing Exceptions

Finalmente, Mockito tambien permite definir el comportamiento de un metodo cuando tiene errores. Para esto en ves de thenReturn() utilizamos el metodo thenThrow().

Finalmente, nuestra clase quedaria de esta forma, teniendo en cuenta que cuando IllegalStateException es tirada, tenemos un try-catch en RemoteFxRateService que devuelve un BigDecimal de -1.00.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class FxConverterTest {

	//Mock object
	private RemoteFxRateService service = mock(RemoteFxRateService.class);
	private FxConverter converter = new FxConverter(service);

	@Test
	@DisplayName("Given 100 USD convert to USD. Return 100.00")
	void convertSameCurrency() {
		when(service.getRate("USD", "USD")).thenReturn("1.00");

		BigDecimal result = converter.convert("USD", "USD", "100.00");
		assertEquals("100", result.toString());
	}

	@Test
	@DisplayName("Given 100 USD convert to EUR. Return 84.97")
	void convertDifferentCurrency() {
		when(service.getRate("USD", "EUR")).thenReturn("0.8497");

		BigDecimal result = converter.convert("USD", "EUR", "100.00");
		assertEquals("84.97", result.toString());
	}

	@Test
	@DisplayName("Given any other args. Return -1.00")
	void convertWithException() {
		when(service.getRate(anyString(), anyString()))
					.thenThrow(new IllegalStateException);

		BigDecimal result = converter.convert("USD", "EUR", "100");
		assertEquals("-1.00", result.toString());
	}
}

Esta es la parte final del Unit Testing. Ten en cuenta que usando todos estos poderosos conocimientos, podremos lograr crear Unit Tests de calidad para nuestras aplicaciones en Java.