Injección de dependencias en Java para dummies

Introducción

Cuando se habla de test unitarios siempre se acaba hablando de inyección de dependencias pero qué es y porque es tan importante cuando queremos probar nuestro código.

Qué es

Como comenté en el artículo sobre mis primeros pasos con test unitarios, la inyección de dependencias consiste en pasar a un objeto sus objetos colaboradores, es decir, los objetos que utiliza.

 

Diagrama 1: Código de producción

 

En la diagrama anterior podemos ver una clase UserService que permite validar que un usuario puede logearse utilizando el método signIn.
Para obtener la información del usuario a partir de su email necesita utilizar la clase UserDAOOracle.

 

Diagrama 2: Código de prueba

 

En este segundo diagrama podemos ver la clase UserService que hace lo mismo que en el diagrama anterior pero que para obtener la información del usuario utiliza FakeUserDAO.
Esta clase FakeUserDAO la hemos creado nosotros para controlar el resultado que devuelve y así no tener que depender de la base de datos.

Para poder pasar del primer diagrama al segundo tenemos que hacer algo para que la clase UserService pueda utilizar la clase FakeUserDAO en vez de UserDAOOracle, y ese algo es la inyección de dependencias.

Porqué

La inyección de dependencias nos permite que nuestro código sea más flexible ya que hace que una clase pueda cambiar sus colaboradores sin necesidad de cambiar su código.

En relación a los test unitarios, la inyección de dependencias permite que nuestros test unitarios sean repetibles (puedes ver las propiedades de un buen test unitario aquí).

Un test es repetible cuando el resultado es siempre el mismo: pasa (verde) o no pasa (rojo).
Si el test no fuera repetible no confiaríamos en su resultado y no nos sería de utilidad.

La inyección de dependencias hace que nuestros test sean repetible ya que nos permite controlar los colaboradores.

Así, usamos los objetos reales cuando estamos en el código de producción y objetos que podemos manipular (dobles de test) cuando estamos con los test unitarios.

Cómo

Para que FakeUserDAO pueda sustituir a UserDAOOracle debe de tener la misma API, una serie de métodos públicos idénticos.
La forma habitual de hacer eso es crear una interfaz con los métodos que tengan que implementar ambas.
Así, en este caso podíamos crear una interfaz UserDAO e inyectar UserDAO a UserService:

 

Hay cuatro formas principalmente de inyectar dependencias:

  1. En el propio método: Añadir la dependencia en el método que la utiliza. Así, en nuestro ejemplo  añadiríamos la dependencia en el método signIn.
  2. Constructor: como su nombre indica añadimos las dependencias en el constructor
  3. Método o Setter: Se puede crear un método por cada dependencia o uno para todas.
  4. Campos públicos: Las dependencias son campos públicos que son declarados en el constructor por lo que podemos cambiarlos.

Para no hacer el artículo muy largo vamos a ver un ejemplo del método que suelo utilizar habitualmente, que es el constructor.
Suelo utilizarlo porque creo que es el más simple ya que si no creamos el constructor por defecto (sin parámetros) al crear una instancia el compilador nos obliga a utilizar el constructor con las dependencias, por lo que no necesitamos recordar como inyectar las dependencias.A continuación podemos ver el código de la clase UserService y como sería el código en producción y el de test:

Podemos ver que en el código de producción utilizamos UserDAOOracle y que en el código de prueba utilizamos FakeUserDAO debido a que ambas implementan la interfaz UserDAO.
El método UserService.signIn solo lanza una excepción cuando no se ha encontrado el usuario utilizando el email de este.Quería haber simplificado al máximo el código pero no me gusta nada devolver null en ningún método y por eso lo que he hecho es devolver un objeto NoUser, que hereda de User y que sobrescribe el método isEmpty devolviendo siempre true:

 

Tips

Algunos pequeños consejos que me hubieran gustado escuchar cuando empecé:

  • Si no sueles inyectar dependencias empieza a hacerlo ahora mismo, aunque no creas sus test unitarios. Sin inyectar dependencias es mucho más complicado probar tu código.
  • No utilices static, ni clases que implementen el patrón Singleton porque te va a resultar más complicado el inyectar las dependencias y crear test unitarios. Hay algunas excepciones pero son muy pocas.
  • Si tienes que inyectar más de dos dependencias en una clase puede ser un problema de diseño. Comprueba si la clase no está haciendo más de una cosa a la vez y está incumpliendo el principio de única responsabilidad.
  • Cuando quieras crear test unitarios de clases existentes y que no tengan inyectadas las dependencias sin romper nada creo que el siguiente enlace que contiene un video de Sandro Mancuso te puede ayudar:

http://craftedsw.blogspot.nl/2012/12/screencast-testing-and-refactoring.html

¿Conoces algún consejo más sobre la inyección de dependencias?

Modificado 17-05-2014: Después de leer este artículo de Carlos Ble he decido cambiar el nombre del método UserDAO.findUserByEmail a UserDAO.findUserBy porque es cierto que es redundante al pasar como parámetro un objeto de tipo Email.

  • kikers25

    Gracias por aclararlo, que no había quedado bien explicado 🙂

  • Me alegro que te haya resultado positivo el post sobre los nombres 😉 Gracias Enrique

  • Gracias a tí por escribir el post