Introduction to integration tests in Spring Framework

Making the code testable is one of the crucial points of maintainable software. Without the tests, the code is exposed to surprising bugs on every change.

Imagine that you have a method that sends an e-mail which template is taken from the database. Now your boss demands to transfer all templates to plaintext files. If you're vigilant, you'll probably manually test the sending of all mails that have the templates. But meantime, your boss demands to correct a priority bug. They are a lot of chance that you will forgot about testing all cases. In this particular situations, a better solution would be to write an automatic test. This test could, for example, checks if the mail has a correct content and if is correctly send. This type of tests is known as integration tests.

As you can guess, the subject of this article will be the integration testing. This article is the first from two articles dedicated to integration testing. More precisely, we'll focus on these tests in the case of Spring web application. In this first article we'll start by define a idea of integrating tests. After that we'll present some of basic testing tools in Spring framework. The last part will be destined to sample code. This code will explore the basics of integration testing for Spring web applications.

What is integration testing ?

The best way to understand well the idea hidden behind integration testing, it's to begin by explain the unit testing concept. Unit testing is a methodology that checks the code unitary, method by method. Integration testing is a mix of unit tests. It means that integration test must prove that pieces of code (for example: several methods) work correctly together.

For example, imagine that we are developing an e-commerce web app. In this system user can buy only the products in stock. To do that, our code checks firstly if demanded product is available (method isInStock()). If the product is available, the system puts it inside customer's shopping cart with the method addToBasket(). Both methods are encapsulated in controller's method addFromStock(). The unit tests for this case will test separetely isInStock() and addToBasket() methods. The integration test will check addFromStock().

Integration testing for Spring web applications

In Spring web application world, integration testing means the tests of whole components, such as controllers actions, services operations on database, autowiring features or transaction management. To accomplish all these test scenarios, Spring provides a lot of features, placed inside org.springframework.test package:
- caching context: both for WebApplicationContext and ApplicationContext, this feature permits to cache context used while tests. Thanks to it, we can avoid to load the context at every test case, and, in consequence, save precious seconds.
- shared context: the production context can be reused inside integration tests. More, some of its beans or configurations, can be overriden.
- transaction management: you can decide to insert tested data into database or rollback transaction after the test execution.
- annotation facility: forget about the necessity of configuring XML files specially for test cases. Spring's test package provides a lot of annotations for that

Before writing our own integration test sample, we need to learn some basic test annotation:

Note that in the case of @RunWith we talk about a kind of TestContextManager. In reality, it's one of the key parts of Spring test package. As its name indicates, TestContextManager manages test context and publishes to execution listeners. As you can suppose, they are two supplementary key parts: test context and test listeners. The first one is represented by TestContext interface with the default implementation DefaultTestContext. This class encapsulates current context used. As to test listeners, test context manager publishes events when some actions occur. The listeners can be notified before preparing class instance, before or after executing tested method.

We must talk about SpringJUnit4Class runner too. Placed in org.springframework.test.context.junit4 package, this class makes a bridge between JUnit test framework and Spring classes. SpringJUnit4Class constructs a text context manager. Thanks to it, all necessary information can be transmitted directly to tests cases. These test cases are constructed inside, through protected Object createTest(). SpringJUnit4Class not only constructs the test methods but also grafts to JUnit execution flow. Thanks to statement classes from the package org.springframework.test.context.junit4.statements, Spring's runner can invoke additional actions, par example, after test method execution.

Spring integration testing sample

After a long introduction, it's a time to make some integration tests. We'll start slowly, by simple tests for services layer. Tested methods won't be displayed. All didactic effort will be focused on integration testing code.

First, we'll prepare test configuration files, loaded to the context with @ContextConfiguration annotation. At this moment, only one configuration file will be different: database configuration. We'll use separate database to execute our tests. So, they are no chance to change a production data. Datasource test file is called dataSource-test.xml and is included into test application context as below:


Before pass to testing code, create database with below code:

CREATE DATABASE testing;
CREATE TABLE IF NOT EXISTS products (
  id INT(11) NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  PRIMARY KEY (id)
);

Our test project will be composed by classes manipulating products table. We can retrieve some classes and interfaces: ProductService and ProductServiceImpl represent business logic, Product class to illustrate database entity and ProductRepository to make some database queries. Our test case will check if one product can be correctly inserted into database. ProductService interface has two methods: addProduct and isInStock. The first one adds a product into database and the second checks if the product has been inserted into database. Let's take a look on our test case:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext-test.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class TestProduct {

  private static int executingTimes = 0;

  @Autowired
  private ProductService productService;

  @Timed(millis=1000)
  @Repeat(3)
  @Test
  public void inStockChecking() {
    System.out.println("Executing "+ (++executingTimes) + " time(s)");
    try {
      Thread.sleep(5000);
    } catch (Exception e) {
      e.printStackTrace();
    }
    Product product = new Product();
    product.setName("milk 2%");
    try {
      boolean inStock = productService.isInStock(product);
      assertTrue("Product ("+product+") should be in stock", inStock == true);
    } catch (Exception e) {
      fail("Product was not correctly retreived");
    }
  }

  @Test
  @Transactional
  public void addingNewProduct() {
    Product milk = new Product();
    milk.setName("milk 15%");
    try {
      productService.addProduct(milk);
    } catch (Exception e) {
    fail("Product was not correctly add because of "+e.getMessage());
    }
  }
}

As you can see, the first annotation indicates the runner for this test. It will be SpringJUnit4Runner. The second annotation, @ContextConfiguration, shows the paths where configuration files are placed. The last annotation, @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true), configures transaction management. The first parameter indicates the manager to use (bean identified with identifier "transactionManager"). The second parameter is used to configure default rollback strategy. In our case, we want to rollback all transactions. It means that they won't be commited into database.

Now, let's analyse the first test method: inStockChecking. Thanks to @Timed and @Repeat annotations we know that it should be executed 3 times in 1 second. But if you are looking to its content, you'll see that it's impossible because of 5-seconds thread sleeping at each call. It's why this method will fail with following message:

java.util.concurrent.TimeoutException: Test took 15270 ms; limit was 1000 ms.
  at org.springframework.test.context.junit4.statements.SpringFailOnTimeout.evaluate(SpringFailOnTimeout.java:71)
  at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
  at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
  at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
  at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
  at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
  at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
  at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
  at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

The second method interacts more with transaction managing. In fact, thanks to @TransactionConfiguration annotation, the test transactions won't be committed into database. To make this annotation works, you must annotate all test methods with @Transactionnal. In the case when you want to commit transactions from one method and do not this for another, you can use @Rollback annotation. In following situation:

@Test
@Transactionnal
@Rollback(false)
public void realDbInsert() {
	// ...
}

@Test
@Transactionnal
public void notRealInsert() {
	// ...
}


- realDbInsert method will insert the changes into database because of @Rollback(false) which means no rollback for transactions.
- notRealInsert won't do that. It will inherit transaction configuration from @TransactionConfiguration and its defaultRollback property.

Thanks to these 2 methods we can also distinguish unit testing from integration testing. Unit testing sample is the first method where we make only one operation. The second method, addingNewProduct, is integration test case because the adding operation involves the checking one.

This article presented the basics of integration testing in Spring. In its first part, some of theoretical information was described. After, we discovered the basic annotations specific for Spring integration testing. Thanks to them, we could start to talk about main classes used by Spring to execute test methods (SpringJUnit4Class, TestContextManager, TestContext). The last part learned us how to write a simple test with Spring. It also explained in another way the difference between unit and integration test. The next article from this series will be dedicated to more complicated integration tests, involving controllers, security context and validation. You can read it right now. Just click to link that will redirect you to article about Spring integration testing with controllers.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!