By Subham Aggarwal | 6/26/2017 | General |Beginners

Testing Spring Boot apps

Testing Spring Boot apps

Till now, we’ve gone over the basics of Spring Boot in our previous tutorials and identified the power of various Spring Boot starters. In this article we will cover the following topics:

  • Creating tests for MVC Controllers
  • Automatically configuring a database schema and populating it with data
  • Writing tests using Cucumber

 

We will see how to use the Spring JUnit integration to create unit tests. Next, we will explore the options of setting up the database with test data to test against it. We will then look to the Behavior Driven Development tools, Cucumber and Spock, and see how they integrate with Spring Boot.

Integration testing

We will now create some basic tests to test our web application to ensure that all the controllers expose the expected RESTful URLs on which we can rely on as the service API. This type of testing is a bit beyond what is commonly known as Unit Testing as it tests the entire web application, requires the application context to be fully initialized, and all the beans should be wired together in order to work. This kind of testing is sometimes referred as Integration or Service Testing.

Getting Started

Spring Boot gets us going by already creating a placeholder test file, StudentApplicationTests.java, in the src/test/java/com/discoversdk directory at the root of our project with the following content:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = StudentApplication.class)
public class StudentApplicationTests {

 @Test
 public void contextLoads() {
 }
}

 

In our build.gradle file, we already added a test dependency as:

testCompile("org.springframework.boot:spring-boot-starter-test")

 

We will extend our basic test template to include extensive Integration tests:

import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = StudentApplication.class)
@WebIntegrationTest("server.port:0")
public class StudentApplicationTests {
 

 @Autowired
 private WebApplicationContext context;

 

In order to be able to use the jsonPath(…) matcher at runtime, we will also need to add the following dependency to the dependencies {…} block in our build.gradle file:

testRuntime("com.jayway.jsonpath:json-path")


Execute the tests by running
./gradlew clean test. 

What just happened?

We will first look at the following annotations that have been declared for the StudentApplicationTests class:

  • @RunWith(SpringJUnit4ClassRunner.class): This is a standard JUnit annotation that we can configure so as to use the SpringJUnit4ClassRunner providing functionality of Spring Test Context framework to the standard JUnit tests.
  • @SpringApplicationConfiguration(classes = StudentApplication.class): This is a Spring Boot annotation that is used to determine how to load and configure the Spring Application Context for the integration tests. It is a meta-annotation that contains the ContextConfiguration annotation, which instructs the testing framework to use Spring Boot's SpringApplicationContextLoader for application context creation.
  • @WebIntegrationTest("server.port:0"): This is an annotation that indicates to Spring Boot that the current test is an integration test and will require a complete context initialization and application startup, as if it were the real deal. This annotation is usually included along with @SpringApplicationConfiguration for integration tests. The server.port:0 value is used to tell Spring Boot to start the Tomcat server on a randomly chosen http port, which we will later obtain by declaring the @Value("${local.server.port}") private int port; value field. This ability to select a random http port is very handy when running tests on a Jenkins or any other CI server where, if multiple jobs are running in parallel, you could get a port collision.

 

With the class annotations magic dispelled, let's look at the content of the class itself. As this is a Spring Boot test, we can declare any objects that are managed by Spring to be @Autowired during the execution or set to a specific environment value using an @Value annotation. In our test, we autowired the WebApplicationContext and StudentRepository objects, which we will use in the execution of the standard JUnit @Test annotated test cases.

 

MockMvc provides us with a very extensive set of capabilities in order to execute assertions on practically all the things that are related to a web request. It is designed to be used in a method chained fashion, allowing us to link the various tests together and forming a nice continuous logical chain. We have used the following checks in our example:

  • The perform(get(…)) method sets up the web request. In our particular case, we perform a GET request but the MockMvcRequestBuilders class provides you with static helper functions for all the common method calls.
  • The andExpect(…) method can be invoked multiple times where each call represents an evaluation of a condition against the result of the perform(…) call. The argument of this call is any implementation of the ResultMatcher interface along with many stock ones that are provided by the MockMvcResultMatchers static utility class. This really opens up a possibility of having an infinite number of different checks such as verifying the response status, content type, values stored in a session, flash scope, verify redirects, contents of the rendering model or headers, and much more. We will use a third-party json-path add-on library to test the JSON response data in order to ensure that it contains the right elements in the right tree hierarchy, andExpect(jsonPath("$.name").value("Packt")), which validates that we have a name element at the root of the JSON document with a value of Packt.

Using Cucumber

The Cucumber testing framework is another testing tool which is powerful and integrates very well with Spring Boot.

Getting Started

Let us start getting Cucumber integrated with our Spring boot app.

  1. Adding appropriate dependencies is our first step.
testCompile("info.cukes:cucumber-spring:1.2.2")    testCompile("info.cukes:cucumber-java8:1.2.2")    testCompile("info.cukes:cucumber-junit:1.2.2")


Next, we will need to create a test driver class to run Cucumber tests. 

@RunWith(Cucumber.class)
@CucumberOptions(plugin={"pretty", "html:build/reports/cucumber"},  
                glue = {"cucumber.api.spring", "classpath:com.discoversdk"}, 
                monochrome = true)
public class RunCukeTests {
}


We will cover the above annotations shortly. Let us get started with a code sample to write our test: 

@WebAppConfiguration
@ContextConfiguration(classes = {StudentApplication.class, TestMockBeansConfig.class}, loader = SpringApplicationContextLoader.class) 
public class RepositoryStepdefs {
   

   @Autowired
   private WebApplicationContext context;
   

   @Autowired
   private DataSource ds;
   

   @Autowired
   private StudentRepository repository;

   private Student loadedStudent;

   @Given("^([^\\\"]*) fixture is loaded$")
   public void data_fixture_is_loaded(String fixtureName) throws Throwable {
       ResourceDatabasePopulator populator = new ResourceDatabasePopulator(context.getResource("classpath:/" + fixtureName + ".sql"));
       DatabasePopulatorUtils.execute(populator, ds);
   }

   @Given("^(\\d+) students available in the catalogue$")
   public void students_available_in_the_catalogue(int studentCount) throws Throwable {
       assertEquals(studentCount, repository.count());
   }

   @When("^searching for students by roll ([\\d-]+)$")
   public void searching_for_student_by_roll(String roll) throws Throwable {
       loadedStudent = repository.findStudentByRole(roll);
       assertNotNull(loadedStudent);
       assertEquals(roll, loadedStudent.getRoll());
   }

   @Then("^student name will be ([^\"]*)$")
   public void student_name_will_be(String studentName) throws Throwable {
       assertNotNull(loadedStudent);
       assertEquals(studentName, loadedStudent.getName());
   }
}

 

  1. Now, we will need to create a corresponding testing feature definition file named repositories.feature in the src/test/resources/com/test/discoversdk directory at the root of our project with the following content:

 

@txn
Feature: Finding a student by name
 Background: Preload DB Mock Data
   Given discoversdk-students fixture is loaded

 Scenario: Load one student
   Given 3 students available in the catalogue
   When searching for student by roll 123
   Then student name will be Lisa Ray

 

  1. Lastly, we will create one more data SQL file named discoversdk-students.sql in the src/test/resources directory at the root of our project with the following content:

 

INSERT INTO reviewer (id, first_name, last_name) VALUES (5, 'Shrikrishna', 'Holla')
INSERT INTO student (roll, name, reviewer_id, exam_id) VALUES ('978', 'Lisa Raye', 5, 1)

 

  1. Execute the tests by running ./gradlew clean test and the tests should get passed.

 

Now, let’s understand about various annotations we used in the test.

It all starts with the driver harness class, which in our case is RunCukeTests. This class itself does not contain any tests but has two important annotations that stitch things together: @RunWith(Cucumber.class) and @CucumberOptions.

  • @RunWith(Cucumber.class): This is a JUnit annotation that indicates that the JUnit runner should use the Cucumber Feature files to execute the tests.
  • @CucumberOptions: This provides an additional configuration for Cucumber.
    • plugin={"pretty", "html:build/reports/cucumber"}: This tells Cucumber to generate its reports in an html format in the build/reports/cucumber directory.
    • glue = {"cucumber.api.spring", "classpath:com.discoversdk"}: This is a VERY important setting as it tells Cucumber which packages to load and from where to load them during the execution of the tests. The cucumber.api.spring package needs to be present in order to take advantage of the cucumber-spring integration library and the com.discoversdk package is the location of our Step Definition implementation classes.
    • monochrome = true: This tells Cucumber not to print the output with the ANSI color as we integrate with JUnit and it will not look correct in the saved console output files.

 

By Subham Aggarwal | 6/26/2017 | General

{{CommentsModel.TotalCount}} Comments

Your Comment

{{CommentsModel.Message}}

Recent Stories

Top DiscoverSDK Experts

User photo
3355
Ashton Torrence
Web and Windows developer
GUI | Web and 11 more
View Profile
User photo
3220
Mendy Bennett
Experienced with Ad network & Ad servers.
Mobile | Ad Networks and 1 more
View Profile
User photo
3060
Karen Fitzgerald
7 years in Cross-Platform development.
Mobile | Cross Platform Frameworks
View Profile
Show All
X

Compare Products

Select up to three two products to compare by clicking on the compare icon () of each product.

{{compareToolModel.Error}}

Now comparing:

{{product.ProductName | createSubstring:25}} X
Compare Now