Spring: testing con JUnit 4

logo spring

En el tutorial Testing con JUnit 4 vimos la importancia del testing automatizado y los primeros pasos para realizarlo en Java y Android con JUnit 4. En los proyectos en los que utilicemos Spring necesitaremos acceder en algunas clases de tests a los beans proporcionados por el contenedor de inyección de dependencias y, en muchos de ellos, utilizar versiones mocks, stubs, dummys, etc de los mismos.

Spring proporciona soporte para testing, utilizable tanto en JUnit como TestNG, a través del módulo spring-test que además de permitir la inyección de beans en los tests ofrece numerosas utilidades para trabajar con transacciones, lanzar scripts de sql, utilizar controladores de Spring MVC, etc.

En este pequeño tutorial veremos cómo empezar a testear nuestras aplicaciones basadas en Spring con JUnit 4 utilizando este módulo.

Nota: Spring 5 proporciona soporte oficial para JUnit5.

Proyecto para pruebas

Vamos a testear una aplicación muy sencilla basada en Spring 4 (sólo vamos a necesitar el modulo spring-context) que consta de un bean(Dependency) que a su vez depende de otro bean(SubDependency).

package com.danielme.spring.test;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Dependency {

	private final SubDependency subDependency;

	@Value("${url}")
	private String url;

	public Dependency(SubDependency subDependency) {
		super();
		this.subDependency = subDependency;
	}

	public String getClassName() {
		return this.getClass().getSimpleName();
	}

	public String getSubdepedencyClassName() {
		return subDependency.getClassName();
	}

	public int addTwo(int i) {
		return i + 2;
	}	

	public String getUrl() {
		return url;
	}

}
package com.danielme.spring.test;

import org.springframework.stereotype.Component;

@Component
public class SubDependency {

	public String getClassName() {
		return this.getClass().getSimpleName();
	}
}

Obsérvese que la inyección de dependencias en Dependency se hace a través del constructor y que en este caso no es necesario utilizar @Autowired puesto que al haber un sólo constructor Spring se verá forzado a utilizarlo para poder instanciar la clase. Realizar la inyección de dependencias mediante constructor es una buena práctica por muchas razones, una de ellas es que en los tests si hiciera falta podemos construir cualquier dependencia “manualmente” sin utilizar Spring y evitándose tener que crear setters o utilizar reflection para acceder a los atributos en los que hacer la inyección.

La configuración de Spring está definida con JavaConfig. Importamos el fichero /src/main/resources/values.properties con la propiedad “url” a inyectar en Dependency.url

package com.danielme.spring.test;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ComponentScan
@PropertySource("classpath:values.properties")
public class AppConfiguration {

}

Por último, en el pom.xml además de la dependencia de Spring (spring-context), tenemos las dependencias necesarias para utilizar log4j2 integrado con Spring.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.danielme.spring</groupId>
	<artifactId>spring-test</artifactId>
	<version>1.0</version>
	<name>spring-testing</name>
	<packaging>jar</packaging>

	<description>Spring testing</description>

	<properties>
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>				

		<springframework.version>4.3.9.RELEASE</springframework.version>
		<mavencompiler.version>3.2</mavencompiler.version>		
		<log4jframework.version>2.5</log4jframework.version>		
		<slf4j.version>1.7.19</slf4j.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${springframework.version}</version>
		</dependency>


		<!-- Loggin with Log4j2 - Spring integration with SL4J Facade -->

		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>${log4jframework.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-jcl</artifactId>
			<version>${log4jframework.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-slf4j-impl</artifactId>
			<version>${log4jframework.version}</version>
		</dependency>		

	</dependencies>

	<build>

		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>${mavencompiler.version}</version>
				<configuration>
					<encoding>${project.build.sourceEncoding}</encoding>
					<source>${jre.version}</source>
					<target>${jre.version}</target>
				</configuration>
			</plugin>


		</plugins>
	</build>
</project>
Configurar el test con JUnit

Vamos a crear un test de JUnit que pruebe la clase Dependency pero una instancia creada e inyectada por Spring. En esta aplicación de ejemplo tan simple realmente no necesitamos ningún tipo de integración con Spring para hacer los tests, pero con este ejemplo tan sencillo será fácil comprender los conceptos aplicados.

Seguimos los siguientes pasos:

  1. Añadir las dependencias del módulo spring-test y de JUnit 4 (Sprint 5 ya incorporará soporte para JUnit 5).
    
             <junit.version>4.12</junit.version>
    	</properties>
             ...
    	<!-- Testing -->
    
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-test</artifactId>
    			<version>${springframework.version}</version>
    			<scope>test</scope>
    		</dependency>
    
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>${junit.version}</version>
    			<scope>test</scope>
    		</dependency>
    
  2. Utilizar el runner SpringJUnit4ClassRunner.
    @RunWith(SpringJUnit4ClassRunner.class)
    
  3. Importar sólo la configuración de Spring que se usará específicamente en este test con la anotación @ContextConfiguration. Indicaremos las clases o los XML de configuración, ejemplos:
    @ContextConfiguration(classes = {AppConfiguration.class})
    @ContextConfiguration("file:src/main/resources/applicationContext.xml")
    @ContextConfiguration(locations = { "file:src/test/resources/applicationContext.xml" })
    
  4. Si fuera necesario utilizar versiones específicas para testing de los valores contenidos en los ficheros properties de /src/main/resources que estamos leyendo en Spring tenemos que “sobrescribir” esos valores en ficheros properties en /src/test/resources y solicitar a Spring que lea estas propiedades con la anotación @TestPropertySource.

    Para probar esta casuística he creado el fichero /src/test/resources/test.properties. Lo importamos en el test del siguiente modo.

    @TestPropertySource("classpath:test.properties")
    
  5. Por último, realizamos la inyección en atributos de los beans que queramos.
    	@Autowired
    	private Dependency dependency;
    
  6. Con respecto a los logs, en el ejemplo estoy utilizando una configuración de log4j2 específica para los test con el fichero /src/test/resources/log4j2.xml que se usará en lugar de /src/main/resources/log4j2.xml

La clase con nuestros tests queda finalmente así.

package com.danielme.spring.test;
import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.danielme.spring.test.Dependency;
import com.danielme.spring.test.SubDependency;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfiguration.class})
@TestPropertySource("classpath:test.properties")
public class DependencyTest {	

	@Autowired
	private Dependency dependency;
	
	@Test
	public void testDependency(){		
		assertEquals(dependency.getClass().getSimpleName(), dependency.getClassName());		
	}
	
	@Test
	public void testAddTwo(){		
		assertEquals(3, dependency.addTwo(1));		
	}
	
	@Test
	public void testSubdependency(){		
		assertEquals(SubDependency.class.getSimpleName(), dependency.getSubdepedencyClassName());
	}
	
	@Test
	public void testUrl(){		
		assertEquals("http://danielmedina.info", dependency.getUrl());
	}
}

El hecho de integrar Spring con JUnit mediante SpringJUnit4ClassRunner puede suponer un problema: sólo podemos utilizar un runner por test por lo que en los tests integrados con Spring deberíamos renunciar a utilizar otros runners, como por ejemplo el JUnitParamsRunner que vimos en el tutorial de JUnit.

Afortunadamente desde Spring 4.2 hay una forma alternativa de hacer la integración mediante reglas del siguiente modo:

@ContextConfiguration(classes = {AppConfiguration.class})
@TestPropertySource("classpath:test.properties")
public class DependencyTest {	

	@ClassRule 
	public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();	
	@Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

En ambos casos los tests se ejecutan correctamente.

Código de ejemplo

El proyecto completo se encuentra disponible en GitHub (para más información sobre cómo utilizar GitHub, consultar este artículo).

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: