Boot

Spring Boot Configuration Tutorial

1. Introduction

When you first heard about Spring Boot, I am sure your wondered what it is for and what is the advantage of using it. So did I.

Spring Boot as the name suggests handles the bootstrapping of a Spring application with a minimal Spring configuration and thus making the application development quicker and simpler. It comes with a set of starter POMs you can choose from. Based on the starter POM you had selected to use, Spring Boot resolves and downloads an assumed set of dependencies. Thus the developer can focus on developing the business logic while Spring Boot handles the starter Spring configuration required.

In this tutorial, you are going to learn how to use Spring Boot with help of a sample “Store Management” CRUD application.
 

2. Environment

This tutorial assumes that you have basic understanding of Java 1.8, Gradle 2.9, Eclipse IDE (Luna) and Spring framework. Please make sure you have a working environment ready using the following technologies, before you attempt to develop/run the “Store Management” application.

If you had never used these technologies before or do not have a working environment, I would recommend you to please follow the links provided below to secure required knowledge and get your environment up and running, before you proceed with this tutorial.

In addition to the above, the following technologies are used in this tutorial.

3. The “Store Management” Application

3.1. Create and configure a Gradle project in Eclipse IDE

If you had never created a Gradle project using Eclipse IDE, I would recommend you to refer to my previous tutorial Spock Tutorial For Beginners that gives you detailed steps on how to create Gradle Project in Eclipse IDE.

The following is the project structure after creating the Gradle Project and the required java/resource files.

Spring Boot - Gradle Project Structure - Part 1
Spring Boot – Gradle Project Structure – Part 1

Spring Boot - Gradle Project Structure - Part 2
Spring Boot – Gradle Project Structure – Part 2

3.2 build.gradle – Quick Walk through

In the Eclipse IDE, open the build.gradle file that is in the project root directory. Update the file as shown below.

build.gradle

 
buildscript {
	repositories { mavenCentral() }
	dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE") }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'

jar {
	baseName = 'store_management'
	version =  '0.1.0'
}

repositories { mavenCentral() }

sourceCompatibility = 1.8
targetCompatibility = 1.8

sourceSets {
	main {
		java.srcDir "src/main/java"
		resources.srcDir "src/main/resources"
	}
	test {
		java.srcDir "src/test/java"
		resources.srcDir "src/test/resources"
	}
	integrationTest {
		java.srcDir "src/integrationTest/java"
		resources.srcDir "src/integrationTest/resources"
		
		compileClasspath += main.output + test.output
		runtimeClasspath += main.output + test.output
	}
}

configurations {
	integrationTestCompile.extendsFrom testCompile
	integrationTestRuntime.extendsFrom testRuntime
}

dependencies {
	testCompile("org.springframework.boot:spring-boot-starter-test")
	compile("org.springframework.boot:spring-boot-starter-data-jpa")
	compile("org.springframework.boot:spring-boot-starter-thymeleaf")
	compile("mysql:mysql-connector-java:5.1.38")
}

task integrationTest(type: Test) {
	testClassesDir = sourceSets.integrationTest.output.classesDir
	classpath = sourceSets.integrationTest.runtimeClasspath
	outputs.upToDateWhen { false }
}

check.dependsOn integrationTest
integrationTest.mustRunAfter test

tasks.withType(Test) {
	reports.html.destination = file("${reporting.baseDir}/${name}")
}

Let us quickly walk through this build.gradle.

buildscript {
	repositories { 
		mavenCentral() 
	}
	dependencies { 
		classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE")	
	}
}

buildscript is used to add the external dependencies to buildscript classpath. A closure that declares build script classpath and adds dependencies to classpath configuration is passed to buildscript() method.

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'

To apply the required plugins java,eclipse and spring-boot so the associated tasks can be used in the build script as needed.

jar {
	baseName = 'store_management'
	version =  '0.1.0'
}

A jar with the name store_management-0.1.0.jar is created under build/libs folder. You may run the Spring Boot Application using the following command:

gradlew build && java -jar build/libs/store_management-0.1.0.jar
repositories { mavenCentral() }

This closure is used to specify the repositories from where the required dependencies are downloaded from.

sourceCompatibility = 1.8
targetCompatibility = 1.8
  • SourceCompatibility is the Java version compatibility to use when compiling Java source.
  • TargetCompatibility is the Java version to generate classes for.
sourceSets {
	main {
		java.srcDir "src/main/java"
		resources.srcDir "src/main/resources"
	}
	test {
		java.srcDir "src/test/java"
		resources.srcDir "src/test/resources"
	}
	integrationtest {
		java.srcDir "src/integrationtest/java"
		resources.srcDir "src/integrationtest/resources"
		
		compileClasspath += main.output + test.output
		runtimeClasspath += main.output + test.output
	}
}

sourceSets is used to group the source files into logical groups. The source files can be java or resource files. This plugin also has associated compileClasspath and runtimeClasspath.

dependencies {
	testCompile("org.springframework.boot:spring-boot-starter-test")
	compile("org.springframework.boot:spring-boot-starter-data-jpa")
	compile("org.springframework.boot:spring-boot-starter-thymeleaf")
	compile("mysql:mysql-connector-java:5.1.38")
}

This is to define the required dependencies needed for this tutorial. As you had seen we have configured the starter POMs for test, JPA and Thymeleaf. Spring Boot, based on the starter POMs defined, resolves the assumed set of dependencies as shown in the picture below. MySQL is used as database for both integration tests and as the production database.

Spring Boot - Starter POM Dependencies- Part 1
Spring Boot – Starter POM Dependencies- Part 1

Spring Boot - Starter POM Dependencies- Part 2
Spring Boot – Starter POM Dependencies- Part 2

Spring Boot - Starter POM Dependencies- Part 3
Spring Boot – Starter POM Dependencies- Part 3

configurations {
	integrationtestCompile.extendsFrom testCompile
	integrationtestRuntime.extendsFrom testRuntime
}

The integrationtestCompile dependency configuration inherits the dependency configuration required to compile the unit tests. The integrationtestRuntime dependency configuration inherits the dependency configuration required to run the unit tests.

task integrationtest(type: Test) {
	testClassesDir = sourceSets.integrationtest.output.classesDir
	classpath = sourceSets.integrationtest.runtimeClasspath
	outputs.upToDateWhen { false }
}

testClassesDir is set to configure the location for the integration test classes. classpath specifies the classpath used when integration tests are run. outputs.upToDateWhen { false } is set to false so that the integration tests are executed everytime the integrationtest task is invoked.

check.dependsOn integrationtest
integrationtest.mustRunAfter test

As you are aware, build task is combination of check and assemble tasks. As it is self-explanatory, check.dependsOn integrationtest is to make sure integration tests are run when the build task is invoked. integrationtest.mustRunAfter test is to make sure that the unit tests are run before integration test.

tasks.withType(Test) {
	reports.html.destination = file("${reporting.baseDir}/${name}")
}

This is to make sure the unit test and integration test reports are written to different directories.

While searching online for help to efficiently configure the integration tests, I had stumbled upon the following quite useful links.

3.3 The CRUD

StoreManagementApplication.java

package management;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StoreManagementApplication {
	public static void main(String[] args) {
		SpringApplication.run(StoreManagementApplication.class, args);		
	}

}

This is the entry point of the Spring Boot Application. @SpringBootApplication is combination of the annotations @Configuration, @EnableAutoConfiguration and @ComponentScan.

AppInitializer.java

package management.store.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class AppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        WebApplicationContext context = getContext();
        servletContext.addListener(new ContextLoaderListener(context));
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/*");
    }

    private AnnotationConfigWebApplicationContext getContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.setConfigLocation("management.store.config");
        return context;
    }

}

Have you noticed yet, we haven’t created any web.xml at all?

The AppInitializer class configures the required ServletContext programmatically by implementing the interface WebApplicationInitializer thus removing the need to create any web.xml.

The onStartup() is implemented to configure the given ServletContext with any servlets, filters, listeners context-params and attributes necessary for initializing this web application.

The addServlet() registers an instance of DispatcherServlet to be used with ServletContext.

The AnnotationConfigWebApplicationContext is implmentation of WebApplicationContext which scans and accepts classes annotated with @Configuration in the classpath configured by setConfigLocation(). As you can see, we have configured the location as management.store.config, where all the @configuration annotated classes are stored.

WebConfig.java

package management.store.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "management.store.config")
public class WebConfig extends WebMvcConfigurerAdapter {
	
	@Bean
	public TemplateResolver templateResolver(){
		ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
		templateResolver.setPrefix("/WEB-INF/view/");
		templateResolver.setSuffix(".html");
		templateResolver.setTemplateMode("HTML5");
		return templateResolver;
	}
	
	@Bean
	public SpringTemplateEngine templateEngine()
	{
		SpringTemplateEngine templateEngine = new SpringTemplateEngine();
		templateEngine.setTemplateResolver(templateResolver());
		return templateEngine;
	}
	
	   @Bean
	    public ViewResolver getViewResolver() {
	        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
	        resolver.setTemplateEngine(templateEngine());
	        resolver.setOrder(1);
	        return resolver;
	    }
	
 
	}

You might have already noticed that we haven’t created any xml for Spring MVC configuration. The above class provides the Spring MVC configuration programmatically. In our current example, we have configured the ServletContextTemplateResolver with the required details such as resource location (WEB-INF/view) and the type of resource (.html) to resolve the resources.

BaseController.java

package management.store.controller;

public class BaseController {

}

This is the base class for our controller hierarchy.

StoreManagementController.java

package management.store.controller;

import management.store.model.Store;
import management.store.service.StoreManagementService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class StoreManagementController extends BaseController{

	@Autowired
	StoreManagementService storeService;

	@RequestMapping(value = "/loadstore", method = RequestMethod.GET)
	public String storeLoad(Model model) {
		model.addAttribute("store", new Store());
		return "store";
	}

	
	@RequestMapping(value = "/getallstores", method = RequestMethod.GET)
	public String getAllStores(Model model) {
		model.addAttribute("stores", storeService.getAllStores());
		return "storelist";
	}
	
	@RequestMapping(value = "/addstore", method = RequestMethod.POST)
	public String storeAdd(@ModelAttribute Store store, Model model) {
		Store addedStore = storeService.addStore(store);
		model.addAttribute("stores", storeService.getAllStores());
		return "storelist";
	}
	
	@RequestMapping(value = "/deletestore/{id}", method = RequestMethod.GET)
	public String storeDelete(@PathVariable Long id, Model model) {

		storeService.deleteStore(id);
		model.addAttribute("stores", storeService.getAllStores());
		return "storelist";
	}
	
	@RequestMapping(value = "/updatestore", method = RequestMethod.POST)
	public String storeUpdate(@ModelAttribute Store store, Model model) {
		storeService.updateStore(store);
		model.addAttribute("stores", storeService.getAllStores());
		return "storelist";
	}
	
	@RequestMapping(value = "/editstore/{id}", method = RequestMethod.GET)
	public String storeEdit(@PathVariable Long id, Model model) {
		model.addAttribute("store", storeService.getStore(id));
		return "editstore";
	}
}
  • @Controller stereotype annotation indicates that the class is a “Controller” (e.g. a web controller). Service is autowired into the controller. The controller invokes the service methods to perform the required CRUD operations on the database.
  • @RequestMapping is used to map the web requests onto specific handler classes and/or handler methods. As shown in the above example the request /loadstore is mapped to the method storeLoad. RequestMethod.GET is to specify that this is a GET request.
  • @ModelAttribute maps the named model attribute that is exposed to the webview, to the method parameter on which the annotation is defined.
  • @PathVariable maps a method parameter to a URI template variable.

Store.java

package management.store.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;

@Entity
public class Store {

	@Id
	@GeneratedValue(generator="STORE_SEQ")
	@SequenceGenerator(name="STORE_SEQ",sequenceName="STORE_SEQ", allocationSize=1)
	Long storeId;

	String storeName;
	
	String storeStreetAddress;
	
	String storeSuburb;

	String storePostcode;

	public Long getStoreId() {
		return storeId;
	}

	public void setStoreId(Long storeId) {
		this.storeId = storeId;
	}
	
	public String getStoreName() {
		return storeName;
	}

	public void setStoreName(String storeName) {
		this.storeName = storeName;
	}

	public String getStoreStreetAddress() {
		return storeStreetAddress;
	}

	public void setStoreStreetAddress(String storeStreetAddress) {
		this.storeStreetAddress = storeStreetAddress;
	}

	
	public String getStoreSuburb() {
		return storeSuburb;
	}

	public void setStoreSuburb(String storeSuburb) {
		this.storeSuburb = storeSuburb;
	}

	public String getStorePostcode() {
		return storePostcode;
	}

	public void setStorePostcode(String storePostcode) {
		this.storePostcode = storePostcode;
	}

}


The @Entity is the entity class mapped to the corresponding table in the database. The @Id is used to specify the primary key of the entity. @GeneratedValue specifies generation strategy for the primary key field. In this case it is a sequence generated using @SequenceGenerator.A @SequenceGenerator may be specified on the entity class or on the primary key field or property.

StoreRepository.java

package management.store.repo;

import management.store.model.Store;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StoreRepository extends CrudRepository {

}

The @Repository stereotype annotation is to denote the interface to be a repository.

CrudRepository is interface for generic CRUD operations on a repository. The types specified are the type of the entity (in our case Store) and the type of the primary key field(Long in this example).

StoreManagementService.java

package management.store.service;

import java.util.List;
import management.store.model.Store;

public interface StoreManagementService {
	public Store addStore(Store store);
	public List getAllStores();
	public Store getStore(Long id);
	public Store updateStore(Store store);
	public void deleteStore(Long id);
}

This is the parent interface for our service hierachy.

StoreManagementServiceImpl.java

package management.store.service;

import java.util.ArrayList;
import java.util.List;

import management.store.model.Store;
import management.store.repo.StoreRepository;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StoreManagementServiceImpl implements StoreManagementService {

	@Autowired
	StoreRepository storeRepository;

	@Override
	public Store addStore(Store store) {
		if (store == null)
			throw new IllegalArgumentException("Store is null");

		return storeRepository.save(store);
	}

	@Override
	public Store updateStore(Store store) {
		if (store == null)
			throw new IllegalArgumentException("Store is null");

		Store currentStore = getStore(store.getStoreId());

		if (currentStore == null)
			throw new IllegalArgumentException(
					"Store doesnot exist with given store id");

		BeanUtils.copyProperties(store, currentStore);

		return storeRepository.save(currentStore);
	}

	@Override
	public Store getStore(Long id)
	{
		if (id == null) 
			throw new IllegalArgumentException("Store Id is null");
		
		Store st = storeRepository.findOne(id);
		
		if (st == null) throw new IllegalArgumentException("Store with given store id does not exist");
		
		return st;
	}

	@Override
	public List getAllStores() {
		
		List list = new ArrayList();
		
		storeRepository.findAll().forEach(list::add);
		
		return list;
	}

	@Override
	public void deleteStore(Long id) {
		if (id == null)
			throw new IllegalArgumentException("Store Id is null");

		if (getStore(id) != null)
			storeRepository.delete(id);
	}

}

This is the implementation of the parent interface StoreManagementService. The methods are implemented by invoking the methods on StoreRepository that is autowired into the service.

application.properties

spring.datasource.platform=mysql
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/myarticledb
spring.datasource.username=srujana
spring.datasource.password=nimda

schema-mysql.sql

--Integration test also uses MySql database
--To clear the test data created by schema-mysql-test.sql
delete from store where store_name in ("S1", "S2", "S3", "S4", "S5", "S6");

This is the configuration used by the application to connect to MYSQl database. Based on the value XXX configured for spring.datasource.platform SpringApplication looks for and uses the corresponding schema-XXX.sql file to run against the database. For ex. the value for spring.datasource.platform is “mysql” and thus the schema-mysql.sql file is executed when the Spring Boot Application is run.

Here in the schema-mysql.sql we are issuing a delete command. Did you figure out why? Yes, you are right. In our tutorial, as you can see in application-test.properties the integration tests are also configured to use the same database as the production application. Thus before running the production application we are trying to sanitize the production database by removing the testdata. This hassle of explicit clearing of the test data can be overcome by configuring the integration tests to use an embedded database such as h2 while production application can be configured to use a separate database such as MySQL.

editstore.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Store Management</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
	<h1>Store Management</h1>
	<form action="#" th:action="@{/updatestore}" th:object="${store}"
		method="post">
		<table>
			<tr>
				<td>Store Id:</td>
				<td><input type="text" th:field="*{storeId}"
					readonly="readonly" /></td>
			</tr>
			<tr>
				<td>Store Name:</td>
				<td><input type="text" th:field="*{storeName}" /></td>
			</tr>
			<tr>
				<td>Store Street Address :</td>
				<td><input type="text" th:field="*{storeStreetAddress}" /></td>
			</tr>
			<tr>
				<td>Store Suburb:</td>
				<td><input type="text" th:field="*{storeSuburb}" /></td>
			</tr>
			<tr>
				<td>Store PostCode:</td>
				<td><input type="text" th:field="*{storePostcode}" /></td>
			</tr>
			<tr align="center">
				<td><input type="submit" value="Submit" /></td>
				<td><input type="reset" value="Reset" /></td>
			</tr>
		</table>
	</form>
</body>
</html>

This html is rendered to allow the user to perform update operation on the entity. th:object="${store}" is used to collect the form values into the model object.th:action="@{/updatestore}" maps the POST request to the method storeUpdate() of StoreManagementController.

store.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Store Management</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
	<h1>Store Management</h1>
	<form action="#" th:action="@{/addstore}" th:object="${store}"
		method="post">
		<table>
			<tr>
				<td>Store Name:</td>
				<td><input type="text" th:field="*{storeName}"
					th:class="${#fields.hasErrors('storeName')}? fieldError" /></td>
			</tr>
			<tr>
				<td>Store Street Address :</td>
				<td><input type="text" th:field="*{storeStreetAddress}" /></td>
			</tr>
			<tr>
				<td>Store Suburb:</td>
				<td><input type="text" th:field="*{storeSuburb}" /></td>
			</tr>
			<tr>
				<td>Store PostCode:</td>
				<td><input type="text" th:field="*{storePostcode}" /></td>
			</tr>
			<tr align="center">
				<td><input type="submit" value="Submit" /></td>
				<td><input type="reset" value="Reset" /></td>
			</tr>
		</table>
	</form>
</body>
</html>


th:action="@{/addstore}" maps the POST request to the method storeAdd() of StoreManagementController.

storelist.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Store Details</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
	<h1>Store Details</h1>
	<table>
		<tr>
			<th>ID</th>
			<th>NAME</th>
			<th>STREET ADDRESS</th>
			<th>SUBURB</th>
			<th>POSTCODE</th>
		</tr>
		<tr th:each="store : ${stores}">
			<td th:text="${store.storeId}"></td>
			<td th:text="${store.storeName}"></td>
			<td th:text="${store.storeStreetAddress}"></td>
			<td th:text="${store.storeSuburb}"></td>
			<td th:text="${store.storePostcode}"></td>
			<td><a th:href="@{'/editstore/' + ${store.storeId}}">Edit</a></td>
			<td><a th:href="@{'/deletestore/' + ${store.storeId}}">Delete</a></td>
		</tr>
		<tr>
			<td colspan="2">
				<p>
					<a href="/loadstore">Add another store?</a>
				</p>
			</td>
		</tr>
	</table>


</body>
</html>

This is to retrieve the list of entities and display on to the view. th:each="store : ${stores} loops through the list of entities and renders them to the view.

3.4 Unit Test

AbstractUnitTest.java

package management.store;
public abstract class AbstractUnitTest {
}

The base class extended by all the unit test classes in our example.

AbstractControllerUnitTest.java

package management.store;

import management.store.controller.BaseController;

import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

@WebAppConfiguration
public abstract class AbstractControllerUnitTest extends AbstractUnitTest {


	protected MockMvc mockMvc;
	
	protected void setUp(BaseController controller)
	{
		mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
	}
	
}

This is base class for all Controller unit test classes in our example.

StoreContollerMocksTest.java

package management.store.controller;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import management.store.AbstractControllerUnitTest;
import management.store.model.Store;
import management.store.repo.StoreRepository;
import management.store.service.StoreManagementService;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

public class StoreContollerMocksTest extends AbstractControllerUnitTest {

	@Mock
	StoreManagementService storeService;

	@Mock
	StoreRepository storeRepo;

	@InjectMocks
	StoreManagementController storeController;

	@Before
	public void setUp() {
		MockitoAnnotations.initMocks(this);
		setUp(storeController);
	}
	
	//To stub data for service method.

	private List stubDataGetAllStores() {
		List stores = new ArrayList();

		for (int i = 1; i < 3; i++) {
			Store st = new Store();
			st.setStoreName("StubStore" + i);
			stores.add(st);
		}

		return stores;
	}

	@Test
	public void testGetAllStores() throws Exception {

		when(storeService.getAllStores()).thenReturn(stubDataGetAllStores());
		String uri = "/getallstores";

		MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(uri))
				.andReturn();

		int status = result.getResponse().getStatus();

		System.out.println("Status is :" + status);

		verify(storeService, times(1)).getAllStores();

		Assert.assertTrue(status == 200);

	}

}

@Mock is used for creation of mocks for the service and repository beans. @InjectMocks is used to inject the created mocks into the controller. when(storeService.getAllStores()).thenReturn(stubDataGetAllStores()); is to stub the method getAllStores() to return a list of entities. This is a very simple example of using Mockito to write the unit tests.

3.5 Integration Test

AbstractIntegrationTest.java

package management.store;

import management.StoreManagementApplication;

import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(StoreManagementApplication.class)
@ActiveProfiles("test")
public abstract class AbstractIntegrationTest {

}

This is the base class for all the integration tests written in this tutorial. @RunWith(SpringJUnit4ClassRunner.class) indicates that the class should use Spring’s JUnit facilities. @SpringApplicationConfiguration provides an alternative to @ContextConfiguration to configure the ApplicationContext used in tests. @ActiveProfiles("test") is to declare a Spring profile “test” for integration tests. The integration tests, when run with set @ActiveProfles, will look for corresponding application.properties.

application-test.properties

spring.datasource.platform=mysql-test

In our example as the Spring profile is declared as “test”, the integration test looks for application-test.properties.

As per the setting spring.datasource.platform=mysql-test in the application-test.properties, the corresponding schema-mysql-test.sql is exeuted.

schema-mysql-test.sql

CREATE TABLE IF NOT EXISTS store (
  store_id bigint(20) NOT NULL AUTO_INCREMENT,
  store_name varchar(255) DEFAULT NULL,
  store_postcode varchar(255) DEFAULT NULL,
  store_street_address varchar(255) DEFAULT NULL,
  store_suburb varchar(255) DEFAULT NULL,
  PRIMARY KEY (store_id)
);

INSERT IGNORE INTO  store
SET store_name= "S1",store_postcode= "1111",store_street_address="streetaddress1",store_suburb="suburb1";

INSERT IGNORE INTO  store
SET store_name= "S2",store_postcode= "2222",store_street_address="streetaddress2",store_suburb="suburb2";

INSERT IGNORE INTO  store
SET store_name= "S3",store_postcode= "3333",store_street_address="streetaddress3",store_suburb="suburb3";


INSERT IGNORE INTO  store
SET store_name= "S4",store_postcode= "4444",store_street_address="streetaddress4",store_suburb="suburb4";

INSERT IGNORE INTO  store
SET store_name= "S5",store_postcode= "5555",store_street_address="streetaddress5",store_suburb="suburb5";

INSERT IGNORE INTO  store
SET store_name= "S6",store_postcode= "6666",store_street_address="streetaddress6",store_suburb="suburb6";

Integrations tests when invoked, execute this sql script to create the table and insert the data.

AbstractControllerIntegrationTest.java

package management.store;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

@WebAppConfiguration
@IntegrationTest("server.port:0")
@Transactional
public abstract class AbstractControllerIntegrationTest extends AbstractIntegrationTest {

	protected MockMvc mockMvc;
	@Autowired
	protected WebApplicationContext webAppContext;
	
	protected void setUp()
	{
		this.mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();
	}

	
}

@WebAppConfiguration delcares that the ApplicationContext loaded for the integration test should be a WebApplicationContext. @IntegrationTest("server.port:0") is to indicate that the test is an integration test and needs full startup like production application.

Do you know a convenient alternative for combination of @WebAppConfiguration and @IntegrationTest ? You may use @WebIntegrationTest to replace the combination of @WebAppConfiguration and @IntegrationTest. Go ahead and give a try using it.

@Transactional here is used to rollback any transactions performed by the integration tests.

StoreControllerIntegrationTest.java

package management.store.controller;

import management.store.AbstractControllerIntegrationTest;
import management.store.service.StoreManagementService;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

public class StoreControllerIntegrationTest extends AbstractControllerIntegrationTest {

	@Autowired
	StoreManagementService storeManagementService;
	
	@Before
	public void setUp() {
		super.setUp();
	}

	@Test
	public void testPlainLoadStore() throws Exception {

		String uri = "/loadstore";

		MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(uri))
				.andReturn();
		String content = result.getResponse().getContentAsString();
		int status = result.getResponse().getStatus();
		System.out.println("Status is :" + status);
		System.out.println("content is :" + content);
		Assert.assertTrue(status == 200);
		Assert.assertTrue(content.trim().length() > 0);
	}
		
	@Test
	public void testEditStore3() throws Exception {

		String uri = "/editstore/3";

		MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(uri))
				.andExpect(MockMvcResultMatchers.view().name("editstore"))
				.andReturn();
		
		String content = result.getResponse().getContentAsString();
		int status = result.getResponse().getStatus();
		System.out.println("Status is :" + status);
		System.out.println("content is :" + content);
		Assert.assertTrue(status == 200);
		Assert.assertTrue(content.trim().length() > 0);

	}
	
	@Test
	public void testDeleteStore3() throws Exception {

		String uri = "/deletestore/3";

		MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(uri))
				.andReturn();
		
		String content = result.getResponse().getContentAsString();
		int status = result.getResponse().getStatus();
		System.out.println("Status is :" + status);
		System.out.println("content is :" + content);
		Assert.assertTrue(status == 200);
		Assert.assertTrue(content.trim().length() > 0);
	}
}

A method that is annotated with @Before is executed before every test method in the test class. Spring MVC Test is built upon mock implementations of Servlet API that are avialable in spring-test module. You may observe that, as @Transactional is used, any database operations executed while executing the test methods, testDeleteStore3() and testEditStore3() will be rolled back once the test method exited.

4. Execute the tests

1. To run the unit and integration tests together use

gradlew clean build

or

gradlew clean build test integrationtest

2. To run only the unit tests use one of the commands as shown below

gradlew clean build test

or

gradlew clean build test -x integrationtest

3. To run only the integration tests use one of the commands as shown below

gradlew clean build integrationtest

or

gradlew clean build -x test integrationtest

The unit test reports and integration test reports can be found at:

${Project_folder}/build/reports/test/index.html
${Project_folder}/build/reports/integrationtest/index.html

Gradle Build and Test Execution
Gradle Build and Test Execution

Spring Boot Configuration - Unit test summary report
Spring Boot Configuration – Unit test summary report

Spring Boot Configuration - Integration test summary report
Spring Boot Configuration – Integration test summary report

5. Run the application

To run the application use one of the following commands

gradlew bootRun

or

gradlew build && java -jar build/libs/store_management-0.1.0.jar

The application can be accessed using http://localhost:8080/loadstore.

Run CRUD Example - Add New Store
Run CRUD Example – Add New Store

Run CRUD Example - List All Stores
Run CRUD Example – List All Stores

6. References

7. Conclusion

In this tutorial we learnt how to use Spring Boot with help of a CRUD example.

You homework would be to further extend this example to use embedded database like h2 for integration testing, instead of MySQL as mentioned in this example. Hint: Spring profile configuration.

8. Download the Eclipse project

This was a Spring Boot Configuration Tutorial.

Download
You can download the full source code of this example here: Spring Boot Configuration Example

Srujana Cherukuri

Srujana holds Master of Computer Applications degree from Osmania University, Hyberabad, India. She is currently working as a Java/J2EE Consultant and has experience of working with a number of clients in a variety of domains.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Shrinath
Shrinath
4 years ago

Hi,

While building the application I am getting the following error. Please let me know what configuration i am missing.

management.store.controller.StoreControllerIntegrationTest > testPlainLoadStore FAILED
java.lang.IllegalStateException
Caused by: org.springframework.beans.factory.BeanCreationException
Caused by: org.springframework.beans.factory.BeanCreationException
Caused by: org.springframework.beans.factory.BeanCreationException
Caused by: org.springframework.beans.factory.BeanCreationException
Caused by: org.springframework.jdbc.datasource.init.UncategorizedScriptException
Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException
Caused by: java.sql.SQLException

Back to top button