Try with Resources
Sin entrar en profundidad, quizás lo primero es empezar por explicar brevemente qué es Try with Resources en Java. Se trata de una sentencia try que declara uno o varios recursos. Estos recursos se instancian al inicio del try y se cierran y destruyen al salir del bloque try.
A continuación podemos ver algún ejemplo.
try (CloseableHttpClient httpClient = HttpClients.createDefault() ) {
httpClient.execute(request, response -> {
if(response.getCode() == HttpStatus.SC_OK){
responseRequest.set(getStringFromResponse(response));
}
return null;
});
} catch (IOException e) {
this.loggingService.writeLogError(String.format("Error en sendRequest restConsumer: %s", e.getMessage()));
}
try (FileWriter writer = new FileWriter(htmlFile)) {
writer.write(htmlTable);
} catch (IOException e) {
throw new ActionErrorException("Failed to write HTML file to directory: " + outputDirectory, e);
}
return htmlFile;
Como podemos ver en ambos casos en la apertura del try estamos declarando e inicializando un recurso. En el primer ejemplo a través de un método estático y en el segundo caso a través de un constructor.
Lo interesante de esta sentencia es que estos recursos se cierran y se destruyen automáticamente al salir del try, por lo que tenemos perfectamente controlado el consumo y gestión de los mismos.
Implementar con Factoría
Este sistema con try with resources es fundamental para el control de los recursos, pero a priori tiene el problema de que complica el testing unitario, ya que si estamos instanciando estos recursos en tiempo de ejecución dentro de nuestro método, no podemos mockearlos, y por tantoya no podemos hacer tests de nuestro código de forma aislada. Y además no podemos replicar de manera ficticia la respuesta de ciertos métodos, por ejemplo: llamadas http sin realizar las llamadas reales.
Por lo tanto, necesitamos darle una pequeña vuelta. Lo que podemos hacer es delegar la instanciación a un componente o servicio externo al que invocamos desde el try with resources. Básicamente cambiamos la instanciación directa por una factoría. El código de esta factoría es tan sencillo que incluso podemos omitir los tests de esta nueva clase.
Veamos la transformación realizada sobre el ejemplo 1.
Creamos una interfaz de la factoría que vamos a usar para crear la instancia de CloseableHttpClient
public interface HttpClientsFactory {
CloseableHttpClient createHttpClient();
}
Implementamos la factoría, anotándola con @Component para poder inyectarla con Spring.
@Component
public class HttpClientsFactoryImpl implements HttpClientsFactory {
public CloseableHttpClient createHttpClient(){
return HttpClients.createDefault();
}
}
Por último utilizamos esta factoría para generar la instancia en el try with resources.
try (CloseableHttpClient httpClient = httpClientsFactory.createHttpClient() ) {
httpClient.execute(request, response -> {
if(response.getCode() == HttpStatus.SC_OK){
responseRequest.set(getStringFromResponse(response));
}
return null;
});
} catch (IOException e) {
this.loggingService.writeLogError(String.format("Error en sendRequest restConsumer: %s", e.getMessage()));
}
Tests
Una vez que ya estamos usando la factoría ya podemos mockear esta clase de Factoria para que a su vez nos devuelva un mock y podamos ya jugar con Junit tal y como necesitemos.
this.httpClientsFactory = Mockito.mock(HttpClientsFactory.class);
this.httpClient = Mockito.mock(CloseableHttpClient.class);
when(this.httpClientsFactory.createHttpClient()).thenReturn(this.httpClient);
¿Te interesan más temas como este? Aquí te dejamos una lista de entradas que pueden interesarte: