En este tutorial vamos a ver cómo hacer Tests de integración con Spring Boot y Spring Security en servicios REST
0. Índice de contenidos
- 1. Introducción
- 2. Entorno
- 3. Aplicación spring-boot con Spring security
- 4. Definición del servicio REST
- 5. Test de integración
- 6. Configuración y código fuente
- 7. Conclusiones
- 8. Referencias
1. Introducción
Combinando herramientas como spring-boot, spring-security y spring-mvc pueden ofrecernos soluciones muy sencillas de desarrollar y que cubren muchas de las requisitos comunes
de hoy día, como puede ser, aplicar seguridad a la capa de servicios REST de nuestra aplicación (aunque hoy día se está externalizando la seguridad de servicios en herramientas de apoyo, como un API Manager. Más info aquí).
Este tutorial tiene dos objetivos principales:
- Veremos cómo implementar seguridad en servicios REST a través de configuración Spring basado en Java con Spring Security. La aplicación que contendrá dicho servicio estará desarrollada con spring-boot. Para más información sobre spring-boot, puedes consultar este tutorial
- Desarrollaremos tests de integración con spring-boot y TestRestTemplate sobre nuestro servicio, y veremos cómo aplicar la seguridad básica en las peticiones de forma sencilla.
2. Entorno
- Macbook pro core i7 con 16gb RAM
- SO: Yosemite
- IDE: IntelliJ IDEA 14
- Gestión de dependencias: Apache Maven 3.3.3
- Java: JDK 1.8
- Spring: 4.1.6.RELEASE
- Spring-boot: 1.2.5.RELEASE
- Spring-security: 3.2.2.RELEASE
3. Aplicación spring-boot con Spring security
Lo primero que vamos a hacer es configurar nuestra aplicación con spring-boot, y luego le aplicaremos una capa de seguridad básica con autenticación usuario/contraseña.
Se muestra a continuación el código y luego daremos una explicación de cada elemento. Nuestra aplicación estará configurada en una clase Application.java con el siguiente contenido:
@SpringBootApplication @EnableAutoConfiguration @EnableWebSecurity @ComponentScan("com.tutoriales.springbootit") public class Application extends WebSecurityConfigurerAdapter { @Resource public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("user").password("pass").roles("USER"); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Expliquemos el código por línea:
- Línea 1. @SpringBootApplication: Esta anotación indica que se trata de una aplicación basada en spring-boot
- Línea 2. @EnableAutoConfiguration: Anotación de spring-boot que incluye beans recomendados dependiendo de las dependencias que tengas en tu proyecto. Es decir, que si añades una dependencia tomcat-embedded.jar, te creará una instancia de TomcatEmbeddedServletContainerFactory, porque es posible que la uses. Es parte de la «magia» que hace spring-boot por tí
- Línea 3. @EnableWebSecurity: Anotación de spring-security que indica a la configuración de Spring que se van a redefinir métodos de las clases WebSecurityConfigurer o de WebSecurityConfigurerAdapter (que es la que usaremos en este tutorial)
- Línea 4. @ComponentScan(..): Anotación de spring para la búsqueda de componentes por paquetes.
- Línea 5. Nuesta aplicación extiende de WebSecurityConfigurerAdapter para redefinir los métodos de seguridad necesarios. En este caso sólo implementaremos seguridad básica.
- Línea 8. Se redefine este método para aplicar autenticación básica a nuestra aplicación. Servirá para todas las llamadas HTTP que se realicen sobre el contexto de la misma
- Línea 12. Este es el main que será ejecutado desde el test de integración
Con esto ya podemos levantar una aplicación bajo un contenedor de servlet (por defecto Tomcat 8), y con seguridad básica usuario/password implementada. Las posibilidades de configuración de seguridad a nivel de aplicación,
mediante el uso de WebSecurityConfigurerAdapter son muy amplias (de hecho puedes configurar la mayoría de aspectos que te proporciona spring-security).
No hemos necesitado hacer uso de spring-security 4 para realizar esta solución. No obstante, spring-security 4 nos ofrece bastantes funcionalidades nuevas muy interesantes. Os dejo el enlace a su documentación (v4.0.1.RELEASE) aquí.
4. Definición del servicio REST
Para esta solución hemos implementado un servicio REST muy sencillo, con un sólo método GET, que obtiene información del bean Persona. Dicho bean lo podemos ver a continuación:
public class Person implements Serializable { private Long id; private String name; private String surname; private String phoneNumber; //Métodos getter/setter, equals y hashCode }
Y el controlador Spring se muestra a continuación:
@RestController public class PersonRestController { private static final Logger LOG = LoggerFactory.getLogger(PersonRestController.class); private List<Person> personList = new ArrayList<Person>(); { personList.add(new Person(1L, "Pepe", "Ruiz", "666-555-444")); personList.add(new Person(2L, "Paco", "Rodríguez", "633-222-444")); } @RequestMapping(value = "/person/{id}", method = RequestMethod.GET) @ResponseStatus(value = HttpStatus.OK) public Person obtainPersonDetails(@AuthenticationPrincipal Principal principal, @PathVariable("id") long id) throws PersonException { LOG.info(new StringBuilder("User: ").append(principal.getName()).append(" requesting for person: ").append(id).toString()); for(Person person : personList) { if(person.getId().equals(id)) { return person; } } throw new PersonException("Person has not been found!"); } @ExceptionHandler(PersonException.class) @ResponseStatus(value = HttpStatus.NOT_FOUND) public void personExceptionHandler(final Exception exception) { LOG.warn("REST Service exception: " + exception.getMessage()); } }
Como podéis ver, se trata de un servicio muy sencillo que almacena una lista de dos personas, simulando información que viene de, por ejemplo, una base de datos.
En el servicio cabe destacar lo siguiente:
- Línea 1. @RestController: Anotación que combina @Controller con @ResponseBody en los métodos del servicio.
- Línea 15.@AuthenticationPrincipal: Anotación de spring-security que indica que el parámetro de tipo Principal se inyectará con la información del usuario que hace la petición contra el servicio
- Línea 30. Manejador de excepción PersonException en caso de lanzar una petición con una persona que no existe
El servicio devolverá un 200(OK) si se encuentra la persona (y devolverá la misma en formato JSON en el body de la respuesta), o un 404(Not Found) si no se encuentra.
5. Test de integración
Además de un test unitario (que no muestro en el tutorial, y que os lo podéis descargar del repositorio Git (ver Configuración y código fuente)) se ha desarrollado un test de integración.
Gracias a la integración con spring-boot, disponemos de una serie de clases y anotaciones de utilidad que nos servirán para desarrollar tests de integración de forma sencilla.
En concreto, cuando estamos desarrollando este tipo de tests con spring-boot, se realizan las siguientes acciones:
- Se levanta el contenedor de servlets (por defecto Tomcat 8)
- Se levanta el contexto de Spring
- Se ejecutan los tests de la clase de Test (ojo! ¡y de todos los tests de integración del proyecto! Se comparte el contenedor que se ha levantado 😉
- Se le manda señal de shutdown al contenedor de servlets
Nuestro test de integración al servicio REST queda como sigue:
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebIntegrationTest(randomPort = true) public class PersonRestControllerIT { @Value("${local.server.port}") private int port; private RestTemplate restTemplate = new TestRestTemplate("user", "pass"); @Test public void shouldReturnPersonDetails() { final ResponseEntityresponse = restTemplate.getForEntity(getBaseUrl() + "/person/{id}", Person.class, 1L); final Person person = response.getBody(); assertThat(person.getId(), is(1L)); assertThat(person.getName(), is("Pepe")); assertThat(person.getSurname(), is("Ruiz")); assertThat(person.getPhoneNumber(), is("666-555-444")); assertThat(response.getStatusCode(), is(HttpStatus.OK)); } @Test public void shouldNotFindPersonDetailsWhenIdNotFound() { final ResponseEntity response = restTemplate.getForEntity(getBaseUrl() + "/person/{id}", Person.class, 3L); final Person person = response.getBody(); assertThat(person, nullValue()); assertThat(response.getStatusCode(), is(HttpStatus.NOT_FOUND)); } private String getBaseUrl() { return new StringBuilder("http://localhost:").append(port).toString(); } }
Vamos a explicarlo línea a línea:
- Línea 2. @SpringApplicationConfiguration(..): Seguramente nos recuerde a @ContextConfiguration(..), para tests de integración con Spring: enlace. Indica la configuración de la aplicación spring-boot; Le indicaremos la clase de la aplicación que contiene el main de spring-boot.
- Línea 3. @WebIntegrationTest(randomPort = true): Esta es una de las partes más interesantes del test de integración. Esta anotación de spring-boot indica que se trata de un test de integración sobre una aplicación real.
Implementa un bootstrapper (WebAppIntegrationTestContextBootstrapper para ser más exactos) que permite, entre otras cosas, capturar el puerto donde se ha levantado el contenedor de servlets, de forma que lo podamos caputar en el test
(mediante un @Value(..), por ejemplo) y lo podamos usar para hacer las pruebas contra el servicio. A la hora de levantar el contenedor de servlets se le puede indicar si queremos que se levante en algún puerto específico, o en un puerto (libre) aleatorio. - Línea 6. Aquí se inyecta el valor de la propiedad local.server.port, que indica el puerto donde se ha levantado la aplicación spring-boot.
- Línea 9. TestRestTemplate: Clase de spring-boot que extiende del clásico RestTemplate, y que implementa funcionalidad de autenticación básica contra servicios securizados. Usarlo es tan sencillo como añadir el usuario y la contraseña como parámetros del constructor, de forma que irán en las cabeceras de cada una de las peticiones que se realicen contra el servicio que consume.
Con esto tenemos un test de integración atacando al servicio que acabamos de implementar. Éste test estará dentro del ciclo de vida de Maven gracias al plugin maven-failsafe-plugin. Si ejecutamos mvn integration-test, en la consola se muestra lo siguiente:
09:34:47.875 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.tutoriales.springbootit.controller.PersonRestControllerIT] 09:34:47.875 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.tutoriales.springbootit.controller.PersonRestControllerIT] 09:34:47.876 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.tutoriales.springbootit.controller.PersonRestControllerIT] 09:34:47.876 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.tutoriales.springbootit.controller.PersonRestControllerIT] 09:34:47.878 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.tutoriales.springbootit.controller.PersonRestControllerIT] 09:34:47.878 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.tutoriales.springbootit.controller.PersonRestControllerIT] 09:34:48.062 [main] DEBUG o.s.t.c.s.DependencyInjectionTestExecutionListener - Performing dependency injection for test context [[DefaultTestContext@15b204a1 testClass = PersonRestControllerIT, testInstance = com.tutoriales.springbootit.controller.PersonRestControllerIT@77167fb7, testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@2b9627bc testClass = PersonRestControllerIT, locations = '{}', classes = '{class com.tutoriales.springbootit.app.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{server.port:0, org.springframework.boot.test.IntegrationTest=true}', resourceBasePath = '', contextLoader = 'org.springframework.boot.test.SpringApplicationContextLoader', parent = [null]]]]. 09:34:48.108 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence 09:34:48.109 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence 09:34:48.109 [main] DEBUG o.s.core.env.StandardEnvironment - Initialized StandardEnvironment with PropertySources [systemProperties,systemEnvironment] 09:34:48.110 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [integrationTest] PropertySource with search precedence immediately lower than [systemEnvironment] . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.2.5.RELEASE) 2015-07-15 09:34:48.284 INFO 13647 --- [ main] c.t.s.controller.PersonRestControllerIT : Starting PersonRestControllerIT on mbp-jreyes with PID 13647 (/Users/jreyes/workspacesIJ/tutoriales/spring-boot-it/target/test-classes started by jreyes in /Users/jreyes/workspacesIJ/tutoriales/spring-boot-it) 2015-07-15 09:34:48.320 INFO 13647 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@71c3b41: startup date [Wed Jul 15 09:34:48 CEST 2015]; root of context hierarchy 2015-07-15 09:34:48.653 INFO 13647 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]] 2015-07-15 09:34:49.105 INFO 13647 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 0 (http) 2015-07-15 09:34:49.293 INFO 13647 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat 2015-07-15 09:34:49.294 INFO 13647 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.0.23 2015-07-15 09:34:49.375 INFO 13647 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2015-07-15 09:34:49.375 INFO 13647 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1057 ms 2015-07-15 09:34:49.761 INFO 13647 --- [ost-startStop-1] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@108d210b, org.springframework.security.web.context.SecurityContextPersistenceFilter@1873ae24, org.springframework.security.web.header.HeaderWriterFilter@31636d95, org.springframework.security.web.csrf.CsrfFilter@66a50ea6, org.springframework.security.web.authentication.logout.LogoutFilter@4b69f81, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3b9a440c, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@306a8956, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5c0fa2db, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@fe8e14, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4bfc72a, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5010ac52, org.springframework.security.web.session.SessionManagementFilter@5d4c5ab2, org.springframework.security.web.access.ExceptionTranslationFilter@38eb00b9, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@34c78f7] 2015-07-15 09:34:49.804 INFO 13647 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/] 2015-07-15 09:34:49.809 INFO 13647 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*] 2015-07-15 09:34:49.809 INFO 13647 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 2015-07-15 09:34:49.809 INFO 13647 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*] 2015-07-15 09:34:50.014 INFO 13647 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@71c3b41: startup date [Wed Jul 15 09:34:48 CEST 2015]; root of context hierarchy 2015-07-15 09:34:50.061 INFO 13647 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/person/{id}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public com.tutoriales.springbootit.vo.Person com.tutoriales.springbootit.controller.PersonRestController.obtainPersonDetails(java.security.Principal,long) throws com.tutoriales.springbootit.exception.PersonException 2015-07-15 09:34:50.062 INFO 13647 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2015-07-15 09:34:50.063 INFO 13647 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest) 2015-07-15 09:34:50.084 INFO 13647 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2015-07-15 09:34:50.084 INFO 13647 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2015-07-15 09:34:50.116 INFO 13647 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2015-07-15 09:34:50.221 INFO 13647 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 51372 (http) 2015-07-15 09:34:50.223 INFO 13647 --- [ main] c.t.s.controller.PersonRestControllerIT : Started PersonRestControllerIT in 2.109 seconds (JVM running for 2.719) 2015-07-15 09:34:50.353 INFO 13647 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 2015-07-15 09:34:50.353 INFO 13647 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2015-07-15 09:34:50.366 INFO 13647 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 13 ms 2015-07-15 09:34:50.411 INFO 13647 --- [o-auto-1-exec-1] c.t.s.controller.PersonRestController : User: user requesting for person: 1 2015-07-15 09:34:50.472 INFO 13647 --- [o-auto-1-exec-2] c.t.s.controller.PersonRestController : User: user requesting for person: 3 2015-07-15 09:34:50.475 WARN 13647 --- [o-auto-1-exec-2] c.t.s.controller.PersonRestController : REST Service exception: Person has not been found! Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.792 sec 2015-07-15 09:34:50.481 INFO 13647 --- [ Thread-1] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@71c3b41: startup date [Wed Jul 15 09:34:48 CEST 2015]; root of context hierarchy
6. Configuración y código fuente
El fichero de configuración de Maven tendrá esta estructura:
<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.autentia.tutoriales</groupId> <artifactId>spring-boot-IT</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.5.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> <spring.version>4.1.6.RELEASE</spring.version> <spring.security.version>3.2.2.RELEASE</spring.security.version> </properties> <!-- BUILD --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <!-- DEPENDENCIES --> <dependencies> <!-- SPRING --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring.security.version}</version> </dependency> <!-- Test --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.10.19</version> <scope>test</scope> </dependency> <!-- Other dependencies --> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring.security.version}</version> </dependency> </dependencies> <!-- REPOSITORIES --> <repositories> <repository> <id>spring-releases</id> <url>https://repo.spring.io/libs-release</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-releases</id> <url>https://repo.spring.io/libs-release</url> </pluginRepository> </pluginRepositories> </project>
El código del proyecto lo podéis encontrar aquí
7. Conclusiones
Con el desarrollo que se expone en este tutorial se pueden realizar soluciones de integración de componentes independientes dentro de una aplicación empresarial,
como puede ser la definición de una capa de servicios REST.
Las posibilidades que nos ofrece spring-boot para la definición de este tipo de componentes nos ahorra mucho tiempo de desarrollo,
y las clases de utilidad que nos ofrece este módulo de Spring nos permiten encapsular funcionalidad que de otra forma nos llevaría a escribir muchas líneas de código.
Además, nos permiten definir tests independientes del entorno, independientes del servidor de aplicaciones donde esté corriendo la aplicación (ya que levanta uno propio embebido), o configuración según entorno.
Una consideración que hay que tener en cuenta es que estos tests no soportan transaccionalidad (mediante @Transactional), aunque sí si le inyectamos directamente el transactionManager al test.
Por último, existe un módulo específico de testing de spring-boot: spring-boot-starter-test. Dicho módulo provee de algunas herramientas útiles para testing;
por ejemplo, te incluye las librerías mockito, hamcrest, spring-test, junit, y algunas clases de apoyo para testing. En este tutorial se ha optado por incluir las librerías de forma independiente, pero esta es otra opción igual de válida.
Échale un vistazo al apartado de referencias para saber más de este módulo de spring-boot.
Hola. Muy buen tutorial. El proyecto depende de un proyecto padre «spring-boot-starter-parent» pero no se ve la configuracion.
Gracias, Omar! 😉
Puedes ver el pom de «spring-boot-starter-parent» aquí:
https://github.com/spring-projects/spring-boot/blob/master/spring-boot-starters/spring-boot-starter-parent/pom.xml
Un saludo.