Por Rubén Zamora
Especial gracias a Fran Iglesias que fue quién nos enseñó este truco. En un taller que dió en la Pulpocon2022
Muchas veces, cuando construimos el código de producción en base a ejemplos o tests (TDD), nos puede pasar que dejamos cosas para implementar luego, porque usamos dobles de tests, normalmente para retrasar dicha implementación, hasta que tengamos la parte del flujo que queremos implementar lista.
A mí me pasa mucho, cuando hago Outside-in con su doble loop.
Que en el test de aceptación hay partes del código que devuelven null y, como el programa no lo considera un problema, pasa de largo y me sigue diciendo que la salida que espero está mal. Pero veámoslo con un ejemplo:
Voy a desarrollar un nuevo endpoint en mi API, para que me devuelva todas las facturas que tenga guardadas en base de datos. Entonces, lo que tendría que hacer el endpoint /api/invoices es devolverme el siguiente json:
[
{
"id": "bccad74c-1906-4a8e-a4cf-2e49643410fe",
"description": "patatas",
"category": "supermercado",
"amount": "20.45"
},
{
"id": "53359ec0-58eb-4a17-b060-f53b3ec52c4f",
"description": "Compra supermercado",
"category": "supermercado",
"amount": "20.45"
}
]
El test de aceptación nos quedaría algo tal que así:
// InvoicesControllerAcceptanceTest.java
@Test
void should_show_all_invoices() throws Exception {
insertInvoiceWith("bccad74c-1906-4a8e-a4cf-2e49643410fe", "patata", "supermercado", "20.45");
insertInvoiceWith("53359ec0-58eb-4a17-b060-f53b3ec52c4f", "Compra supermercado", "supermercado", "20.45");
mockMvc.perform(get("/api/invoices").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json("[" + "{" + "\"id\": \"bccad74c-1906-4a8e-a4cf-2e49643410fe\"," + " \"description\": \"patatas\"," + " \"category\": \"supermercado\"," + " \"amount\": \"20.45\"" + "}," + " {" + "\"id\": \"53359ec0-58eb-4a17-b060-f53b3ec52c4f\"," + " \"description\": \"Compra supermercado\"," + " \"category\": \"supermercado\"," + " \"amount\": \"20.45\"" + "}" + "]"));
}
Y nuestro código productivo nos ha ido quedando algo así, dónde el controller colabora con un repository para traerse la información de base de datos:
// InvoicesController.java
@GetMapping(value = "/invoices", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<List<Invoice>> retrieveInvoices() {
List<Invoice> responseBody = invoicesRepository.retrieveAllInvoices();
return ResponseEntity.ok(responseBody);
}
// JdbcInvoicesRepository.java
public final class JdbcInvoicesRepository implements InvoicesRepository {
@Override
public List<Invoice> retrieveAllInvoices() {
return null;
}
}
Si lanzamos el test de aceptación nos arroja el siguiente error:
Donde nos comenta que está fallando por intentar transformar un JSON que viene null. Y en este punto, te puedes romper un poco la cabeza, si tienes muchas piezas en el flujo que interactúan entre ellas. Incluso, puede que tengas que llegar a debuguear si has perdido el control en el código, porque no hemos hecho baby steps adecuadamente, por ejemplo.
El caso es que podemos solucionar esto si hacemos lo siguiente, que es tan simple como que, en los nuevos métodos que vamos creando, les decimos que cuando el flujo pase por ahí nos lance una excepción. Entonces nuestro repository quedaría ahora así:
// JdbcInvoicesRepository.java
public final class JdbcInvoicesRepository implements InvoicesRepository {
@Override
public List<Invoice> retrieveAllInvoices() {
throw new UnsupportedOperationException("Please, implement JdbcInvoicesRepository.retrieveAllInvoices");
return null;
}
}
Y si volvemos a lanzar el test de aceptación nos muestra un error diferente ahora:
Por lo que ahora tenemos claro que, nuestro siguiente punto a implementar sería esa parte del flujo.
Pues si estamos utilizando IntelliJ podemos hacer esto configurando el File Template. Para ello, vamos a hacer lo siguiente:
Para este caso buscamos dentro Code en el Template llamado Implemented Method Body, y le ponemos el siguiente fragmento de código.
throw new UnsupportedOperationException("Please, implement ${SIMPLE_CLASS_NAME}.${METHOD_NAME}");
#if ( $RETURN_TYPE != "void" )return $DEFAULT_RETURN_VALUE;#end
También está bien que se lo pongamos al Template New Method Body.
Con ello, ya se nos pondrá automáticamente, al invocar las funcionalidades de implementar método o crear un nuevo método.
Para este caso, hemos utilizado la excepción UnsupportedOperationException porque es nativa del core de Java, pero si quisiéramos tener más semántica en la excepción, podríamos utilizar NotImplementedException de apache commons-lang. Esto demandaría ser consciente de que tenemos que importarla, si tenemos otras librerías que utilicen commons-lang por detrás. (Gracias a Iván por este aporte).
¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.
Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?
Pero espera 🖐 que tenemos un conflicto interno. A nosotros las newsletter nos parecen 💩👎👹 Por eso hemos creado la LEAN LISTA, la primera lista zen, disfrutona y que suena a rock y reggaeton del sector de la programación. Todos hemos recibido newsletters por encima de nuestras posibilidades 😅 por eso este es el compromiso de la Lean Lista