© Marten Deinum, Daniel Rubio, and Josh Long 2017

Marten Deinum, Daniel Rubio and Josh Long, Spring 5 Recipes, https://doi.org/10.1007/978-1-4842-2790-9_16

16. Spring Testing

Marten Deinum, Daniel Rubio2 and Josh Long3

(1)Meppel, Drenthe, The Netherlands

(2)F. Bahia, Ensenada, Baja California, Mexico

(3)Apartment 205, Canyon Country, California, USA

In this chapter, you will learn about basic techniques you can use to test Java applications, as well as the testing support features offered by the Spring Framework. These features can make your testing tasks easier and lead you to better application design. In general, applications developed with the Spring Framework and the dependency injection pattern are easy to test.

Testing is a key activity for ensuring quality in software development. There are many types of testing, including unit testing, integration testing, functional testing, system testing, performance testing, and acceptance testing. Spring’s testing support focuses on unit and integration testing, but it can also help with other types of testing. Testing can be performed either manually or automatically. However, since automated tests can be run repeatedly and continuously at different phases of a development process, they are highly recommended, especially in agile development processes. The Spring Framework is an agile framework that fits these kinds of processes.

Many testing frameworks are available on the Java platform. Currently, JUnit and TestNG are the most popular. JUnit has a long history and a large user group in the Java community. TestNG is another popular Java testing framework. Compared to JUnit, TestNG offers additional powerful features such as test grouping, dependent test methods, and data-driven tests.

Spring’s testing support features have been offered by the Spring TestContext framework, which abstracts the underlying testing framework with the following concepts:

  • Test context: This encapsulates the context of a test’s execution, including the application context, test class, current test instance, current test method, and current test exception.

  • Test context manager: This manages a test context for a test and triggers test execution listeners at predefined test execution points, including when preparing a test instance, before executing a test method (before any framework-specific initialization methods), and after executing a test method (after any framework-specific cleanup methods).

  • Test execution listener: This defines a listener interface; by implementing this, you can listen to test execution events. The TestContext framework provides several test execution listeners for common testing features, but you are free to create your own.

Spring provides convenient TestContext support classes for JUnit and TestNG, with particular test execution listeners preregistered. You can simply extend these support classes to use the TestContext framework without having to know much about the framework details.

After finishing this chapter, you will understand the basic concepts and techniques of testing and the popular Java testing frameworks JUnit and TestNG. You will also be able to create unit tests and integration tests using the Spring TestContext framework.

16-1. Create Tests with JUnit and TestNG

Problem

You want to create automated tests for your Java application so that they can be run repeatedly to ensure the correctness of your application.

Solution

The most popular testing frameworks on the Java platform are JUnit and TestNG. Both JUnit and TestNG allow you to annotate your test methods with the @Test annotation, so an arbitrary public method can be run as a test case.

How It Works

Suppose you are going to develop a system for a bank. To ensure the system’s quality, you have to test every part of it. First, let’s consider an interest calculator, whose interface is defined as follows:

package com.apress.springrecipes.bank;

public interface InterestCalculator {

    void setRate(double rate);
    double calculate(double amount, double year);
}

Each interest calculator requires a fixed interest rate to be set. Now, you can implement this calculator with a simple interest formula, shown here:

package com.apress.springrecipes.bank;

public class SimpleInterestCalculator implements InterestCalculator {

    private double rate;

    public void setRate(double rate) {
        this.rate = rate;
    }


    public double calculate(double amount, double year) {
        if (amount < 0 || year < 0) {
            throw new IllegalArgumentException("Amount or year must be positive");
        }
        return amount * year * rate;
    }
}

Next, you will test this simple interest calculator with the popular testing frameworks JUnit and TestNG (version 5).

Tip

Usually, a test and its target class are located in the same package, but the source files of tests are stored in a separate directory (e.g., test) from the source files of other classes (e.g., src).

Test with JUnit

A test case is simply a public method with the @Test annotation. To set up data, you can annotate a method with @Before. To clean up resources, you can annotate a method with @After. You can also annotate a public static method with @BeforeClass or @AfterClass to have it run once before or after all test cases in the class.

You have to call the static assert methods declared in the org.junit.Assert class directly. However, you can import all assert methods via a static import statement. You can create the following JUnit test cases to test your simple interest calculator.

Note

To compile and run test cases created for JUnit, you have to include JUnit on your CLASSPATH. If you are using Maven, add the following dependency to your project:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

For Gradle, add the following:

testCompile 'junit:junit:4.12'
package com.apress.springrecipes.bank;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;


public class SimpleInterestCalculatorJUnit4Tests {

    private InterestCalculator interestCalculator;

    @Before
    public void init() {
        interestCalculator = new SimpleInterestCalculator();
        interestCalculator.setRate(0.05);
    }


    @Test
    public void calculate() {
        double interest = interestCalculator.calculate(10000, 2);
        assertEquals(interest, 1000.0, 0);
    }


    @Test(expected = IllegalArgumentException.class)
    public void illegalCalculate() {
        interestCalculator.calculate(-10000, 2);
    }
}

JUnit offers a powerful feature that allows you to expect an exception to be thrown in a test case. You can simply specify the exception type in the expected attribute of the @Test annotation.

Test with TestNG

A TestNG test looks similar to a JUnit test, except that you have to use the classes and annotation types defined by the TestNG framework.

Note

To compile and run test cases created for TestNG, you have to add TestNG to your CLASSPATH. If you are using Maven, add the following dependency to your project:

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.11</version>
</dependency>

For Gradle, add the following:

testCompile 'org.testng:testng:6.11'
package com.apress.springrecipes.bank;

import static org.testng.Assert.*;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;


public class SimpleInterestCalculatorTestNGTests {

    private InterestCalculator interestCalculator;

    @BeforeMethod
    public void init() {
        interestCalculator = new SimpleInterestCalculator();
        interestCalculator.setRate(0.05);
    }


    @Test
    public void calculate() {
        double interest = interestCalculator.calculate(10000, 2);
        assertEquals(interest, 1000.0);
    }


    @Test(expectedExceptions = IllegalArgumentException.class)
    public void illegalCalculate() {
        interestCalculator.calculate(-10000, 2);
    }
}
Tip

If you are using Eclipse for development, you can download and install the TestNG Eclipse plug-in from http://testng.org/doc/eclipse.html to run TestNG tests in Eclipse. Again, you will see a green bar if all your tests pass and a red bar otherwise.

One of the powerful features of TestNG is its built-in support for data-driven testing. TestNG cleanly separates test data from test logic so that you can run a test method multiple times for different data sets. In TestNG, test data sets are provided by data providers, which are methods with the @DataProvider annotation.

package com.apress.springrecipes.bank;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;


import static org.testng.Assert.assertEquals;

public class SimpleInterestCalculatorTestNGTests {

    private InterestCalculator interestCalculator;

    @BeforeMethod
    public void init() {
        interestCalculator = new SimpleInterestCalculator();
        interestCalculator.setRate(0.05);
    }


    @DataProvider(name = "legal")
    public Object[][] createLegalInterestParameters() {
        return new Object[][]{new Object[]{10000, 2, 1000.0}};
    }


    @DataProvider(name = "illegal")
    public Object[][] createIllegalInterestParameters() {
        return new Object[][]{
            new Object[]{-10000, 2},
            new Object[]{10000, -2},
            new Object[]{-10000, -2}
        };
    }


    @Test(dataProvider = "legal")
    public void calculate(double amount, double year, double result) {
        double interest = interestCalculator.calculate(amount, year);
        assertEquals(interest, result);
    }


    @Test(
        dataProvider = "illegal",
        expectedExceptions = IllegalArgumentException.class)
    public void illegalCalculate(double amount, double year) {
        interestCalculator.calculate(amount, year);
    }
}

If you run the preceding test with TestNG , the calculate() method will be executed once, while the illegalCalculate() method will be executed three times, as there are three data sets returned by the illegal data provider.

16-2. Create Unit Tests and Integration Tests

Problem

A common testing technique is to test each module of your application in isolation and then test them in combination. You want to apply this skill in testing your Java applications.

Solution

Unit tests are used to test a single programming unit. In object-oriented languages, a unit is usually a class or a method. The scope of a unit test is a single unit, but in the real world, most units won’t work in isolation. They often need to cooperate with others to complete their tasks. When testing a unit that depends on other units, a common technique you can apply is to simulate the unit dependencies with stubs and mock objects, both of which can reduce the complexity of your unit tests caused by dependencies.

A stub is an object that simulates a dependent object with the minimum number of methods required for a test. The methods are implemented in a predetermined way, usually with hard-coded data. A stub also exposes methods for a test to verify the stub’s internal states. In contrast to a stub , a mock object usually knows how its methods are expected to be called in a test. The mock object then verifies the methods actually called against the expected ones. In Java, there are several libraries that can help create mock objects, such as Mockito, EasyMock, and jMock. The main difference between a stub and a mock object is that a stub is usually used for state verification, while a mock object is used for behavior verification.

Integration tests, in contrast, are used to test several units in combination as a whole. They test if the integration and interaction between units are correct. Each of these units should already have been tested with unit tests, so integration testing is usually performed after unit testing.

Finally, note that applications developed using the principle of separating interface from implementation and the dependency injection pattern are easy to test, both for unit testing and for integration testing. This is because that principle and pattern can reduce coupling between different units of your application.

How It Works

First you will explore how to write a unit test for a single class, which will then be extended to testing a class with mocked and/or stubbed colaborates. Finally you will take a look on how to write an integration test.

Create Unit Tests for Isolated Classes

The core functions of your bank system should be designed around customer accounts. First, you create the following domain class, Account, with custom equals() and hashCode() methods:

package com.apress.springrecipes.bank;

public class Account {

    private String accountNo;
    private double balance;


    // Constructors, Getters and Setters
    ...


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return Objects.equals(this.accountNo, account.accountNo);
    }


    @Override
    public int hashCode() {
        return Objects.hash(this.accountNo);
    }
}

Next, you define the following DAO interface for persisting account objects in your bank system’s persistence layer:

package com.apress.springrecipes.bank;

public interface AccountDao {

    public void createAccount(Account account);
    public void updateAccount(Account account);
    public void removeAccount(Account account);
    public Account findAccount(String accountNo);
}

To demonstrate the unit testing concept, let’s implement this interface by using a map to store account objects. The AccountNotFoundException and DuplicateAccountException classes are subclasses of RuntimeException that you should be able to create yourself.

package com.apress.springrecipes.bank;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;


public class InMemoryAccountDao implements AccountDao {

    private Map<String, Account> accounts;

    public InMemoryAccountDao() {
        accounts = Collections.synchronizedMap(new HashMap<String, Account>());
    }


    public boolean accountExists(String accountNo) {
        return accounts.containsKey(accountNo);
    }


    public void createAccount(Account account) {
        if (accountExists(account.getAccountNo())) {
            throw new DuplicateAccountException();
        }
        accounts.put(account.getAccountNo(), account);
    }


    public void updateAccount(Account account) {
        if (!accountExists(account.getAccountNo())) {
            throw new AccountNotFoundException();
        }
        accounts.put(account.getAccountNo(), account);
    }


    public void removeAccount(Account account) {
        if (!accountExists(account.getAccountNo())) {
            throw new AccountNotFoundException();
        }
        accounts.remove(account.getAccountNo());
    }


    public Account findAccount(String accountNo) {
        Account account = accounts.get(accountNo);
        if (account == null) {
            throw new AccountNotFoundException();
        }
        return account;
    }
}

Obviously, this simple DAO implementation doesn’t support transactions. However, to make it thread-safe, you can wrap the map storing accounts with a synchronized map so that it will be accessed serially.

Now, let’s create unit tests for this DAO implementation with JUnit. As this class doesn’t depend directly on other classes, it’s easy to test. To ensure that this class works properly for exceptional cases as well as normal cases, you should also create exceptional test cases for it. Typically, exceptional test cases expect an exception to be thrown.

package com.apress.springrecipes.bank;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;


public class InMemoryAccountDaoTests {

    private static final String EXISTING_ACCOUNT_NO = "1234";
    private static final String NEW_ACCOUNT_NO = "5678";


    private Account existingAccount;
    private Account newAccount;
    private InMemoryAccountDao accountDao;


    @Before
    public void init() {
        existingAccount = new Account(EXISTING_ACCOUNT_NO, 100);
        newAccount = new Account(NEW_ACCOUNT_NO, 200);
        accountDao = new InMemoryAccountDao();
        accountDao.createAccount(existingAccount);
    }


    @Test
    public void accountExists() {
        assertTrue(accountDao.accountExists(EXISTING_ACCOUNT_NO));
        assertFalse(accountDao.accountExists(NEW_ACCOUNT_NO));
    }


    @Test
    public void createNewAccount() {
        accountDao.createAccount(newAccount);
        assertEquals(accountDao.findAccount(NEW_ACCOUNT_NO), newAccount);
    }


    @Test(expected = DuplicateAccountException.class)
    public void createDuplicateAccount() {
        accountDao.createAccount(existingAccount);
    }


    @Test
    public void updateExistedAccount() {
        existingAccount.setBalance(150);
        accountDao.updateAccount(existingAccount);
        assertEquals(accountDao.findAccount(EXISTING_ACCOUNT_NO), existingAccount);
    }


    @Test(expected = AccountNotFoundException.class)
    public void updateNotExistedAccount() {
        accountDao.updateAccount(newAccount);
    }


    @Test
    public void removeExistedAccount() {
        accountDao.removeAccount(existingAccount);
        assertFalse(accountDao.accountExists(EXISTING_ACCOUNT_NO));
    }


    @Test(expected = AccountNotFoundException.class)
    public void removeNotExistedAccount() {
        accountDao.removeAccount(newAccount);
    }


    @Test
    public void findExistedAccount() {
        Account account = accountDao.findAccount(EXISTING_ACCOUNT_NO);
        assertEquals(account, existingAccount);
    }


    @Test(expected = AccountNotFoundException.class)
    public void findNotExistedAccount() {
        accountDao.findAccount(NEW_ACCOUNT_NO);
    }
}

Create Unit Tests for Dependent Classes Using Stubs and Mock Objects

Testing an independent class is easy, because you needn’t consider how its dependencies work and how to set them up properly. However, testing a class that depends on results of other classes or services (e.g., database services and network services) would be a little bit difficult. For example, let’s consider the following AccountService interface in the service layer:

package com.apress.springrecipes.bank;

public interface AccountService {

    void createAccount(String accountNo);
    void removeAccount(String accountNo);
    void deposit(String accountNo, double amount);
    void withdraw(String accountNo, double amount);
    double getBalance(String accountNo);
}

The implementation of this service interface has to depend on an AccountDao object in the persistence layer to persist account objects. The InsufficientBalanceException class is also a subclass of RuntimeException that you have to create.

package com.apress.springrecipes.bank;

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    public AccountServiceImpl(AccountDao accountDao) {
        this.accountDao = accountDao;
    }


    public void createAccount(String accountNo) {
        accountDao.createAccount(new Account(accountNo, 0));
    }


    public void removeAccount(String accountNo) {
        Account account = accountDao.findAccount(accountNo);
        accountDao.removeAccount(account);
    }


    public void deposit(String accountNo, double amount) {
        Account account = accountDao.findAccount(accountNo);
        account.setBalance(account.getBalance() + amount);
        accountDao.updateAccount(account);
    }


    public void withdraw(String accountNo, double amount) {
        Account account = accountDao.findAccount(accountNo);
        if (account.getBalance() < amount) {
            throw new InsufficientBalanceException();
        }
        account.setBalance(account.getBalance() - amount);
        accountDao.updateAccount(account);
    }


    public double getBalance(String accountNo) {
        return accountDao.findAccount(accountNo).getBalance();
    }
}

A common technique in unit testing to reduce complexity caused by dependencies is to use stubs. A stub must implement the same interface as the target object so that it can substitute for the target object. For example, you can create a stub for AccountDao that stores a single customer account and implements only the findAccount() and updateAccount() methods, as they are required for deposit() and withdraw().

package com.apress.springrecipes.bank;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;


public class AccountServiceImplStubTests {

    private static final String TEST_ACCOUNT_NO = "1234";
    private AccountDaoStub accountDaoStub;
    private AccountService accountService;


    private class AccountDaoStub implements AccountDao {

        private String accountNo;
        private double balance;


        public void createAccount(Account account) {}
        public void removeAccount(Account account) {}


        public Account findAccount(String accountNo) {
            return new Account(this.accountNo, this.balance);
        }


        public void updateAccount(Account account) {
            this.accountNo = account.getAccountNo();
            this.balance = account.getBalance();
        }
    }


    @Before
    public void init() {
        accountDaoStub = new AccountDaoStub();
        accountDaoStub.accountNo = TEST_ACCOUNT_NO;
        accountDaoStub.balance = 100;
        accountService = new AccountServiceImpl(accountDaoStub);
    }


    @Test
    public void deposit() {
        accountService.deposit(TEST_ACCOUNT_NO, 50);
        assertEquals(accountDaoStub.accountNo, TEST_ACCOUNT_NO);
        assertEquals(accountDaoStub.balance, 150, 0);
    }


    @Test
    public void withdrawWithSufficientBalance() {
        accountService.withdraw(TEST_ACCOUNT_NO, 50);
        assertEquals(accountDaoStub.accountNo, TEST_ACCOUNT_NO);
        assertEquals(accountDaoStub.balance, 50, 0);
    }


    @Test(expected = InsufficientBalanceException.class)
    public void withdrawWithInsufficientBalance() {
        accountService.withdraw(TEST_ACCOUNT_NO, 150);
    }
}

However, writing stubs yourself requires a lot of coding . A more efficient technique is to use mock objects. The Mockito library is able to dynamically create mock objects that work in a record/playback mechanism.

Note

To use Mockito for testing, you have to add it to your CLASSPATH. If you are using Maven, add the following dependency to your project:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.7.20</version>
    <scope>test</scope>
</dependency>

Or when using Gradle, add the following:

testCompile 'org.mockito:mockito-core:2.7.20'
package com.apress.springrecipes.bank;

import org.junit.Before;
import org.junit.Test;


import static org.mockito.Mockito.*;

public class AccountServiceImplMockTests {

    private static final String TEST_ACCOUNT_NO = "1234";

    private AccountDao accountDao;
    private AccountService accountService;


    @Before
    public void init() {
        accountDao = mock(AccountDao.class);
        accountService = new AccountServiceImpl(accountDao);
    }

    @Test
    public void deposit() {
        // Setup
        Account account = new Account(TEST_ACCOUNT_NO, 100);
        when(accountDao.findAccount(TEST_ACCOUNT_NO)).thenReturn(account);


        // Execute
        accountService.deposit(TEST_ACCOUNT_NO, 50);


        // Verify
        verify(accountDao, times(1)).findAccount(any(String.class));
        verify(accountDao, times(1)).updateAccount(account);


    }

    @Test
    public void withdrawWithSufficientBalance() {
        // Setup
        Account account = new Account(TEST_ACCOUNT_NO, 100);
        when(accountDao.findAccount(TEST_ACCOUNT_NO)).thenReturn(account);


        // Execute
        accountService.withdraw(TEST_ACCOUNT_NO, 50);


        // Verify
        verify(accountDao, times(1)).findAccount(any(String.class));
        verify(accountDao, times(1)).updateAccount(account);


    }

    @Test(expected = InsufficientBalanceException.class)
    public void testWithdrawWithInsufficientBalance() {
        // Setup
        Account account = new Account(TEST_ACCOUNT_NO, 100);
        when(accountDao.findAccount(TEST_ACCOUNT_NO)).thenReturn(account);


        // Execute
        accountService.withdraw(TEST_ACCOUNT_NO, 150);
    }
}

With Mockito, you can create a mock object dynamically for an arbitrary interface or class. This mock can be instructed to have certain behavior for method calls, and you can use it to selectively verify whether something has happened. In your test you want that in the findAccount method that a certain Account object is returned. You use the Mockito.when method for this, and you can then either return a value, throw an exception, or do more elaborate things with an Answer. The default behavior for the mock is to return null. You use the Mockito.verify method to do selective verification of actions that should have happened. You want to make sure that the findAccount method is called and that the account gets updated.

Create Integration Tests

Integration tests are used to test several units in combination to ensure that the units are properly integrated and can interact correctly. For example, you can create an integration test to test AccountServiceImpl using InMemoryAccountDao as the DAO implementation.

package com.apress.springrecipes.bank;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;


public class AccountServiceTests {

    private static final String TEST_ACCOUNT_NO = "1234";
    private AccountService accountService;


    @Before
    public void init() {
        accountService = new AccountServiceImpl(new InMemoryAccountDao());
        accountService.createAccount(TEST_ACCOUNT_NO);
        accountService.deposit(TEST_ACCOUNT_NO, 100);
    }


    @Test
    public void deposit() {
        accountService.deposit(TEST_ACCOUNT_NO, 50);
        assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 150, 0);
    }


    @Test
    public void withDraw() {
        accountService.withdraw(TEST_ACCOUNT_NO, 50);
        assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 50, 0);
    }


    @After
    public void cleanup() {
        accountService.removeAccount(TEST_ACCOUNT_NO);
    }
}

16-3. Implement Unit Testing for Spring MVC Controllers

Problem

In a web application, you want to test the web controllers developed with the Spring MVC framework.

Solution

A Spring MVC controller is invoked by DispatcherServlet with an HTTP request object and an HTTP response object. After processing a request, the controller returns it to DispatcherServlet for rendering the view. The main challenge of unit testing Spring MVC controllers, as well as web controllers in other web application frameworks, is simulating HTTP request objects and response objects in a unit testing environment. Fortunately, Spring supports web controller testing by providing a set of mock objects for the Servlet API (including MockHttpServletRequest, MockHttpServletResponse, and MockHttpSession).

To test a Spring MVC controller’s output, you need to check whether the object returned to DispatcherServlet is correct. Spring also provides a set of assertion utilities for checking the contents of an object.

How It Works

In your bank system, suppose you are going to develop a web interface for bank staff to input the account number and amount of a deposit. You create a controller named DepositController using the techniques you already know from Spring MVC.

package com.apress.springrecipes.bank.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;


@Controller
public class DepositController {


    private AccountService accountService;

    @Autowired
    public DepositController(AccountService accountService) {
        this.accountService = accountService;
    }


    @RequestMapping("/deposit.do")
    public String deposit(
        @RequestParam("accountNo") String accountNo,
        @RequestParam("amount") double amount,
        ModelMap model) {
        accountService.deposit(accountNo, amount);
        model.addAttribute("accountNo", accountNo);
        model.addAttribute("balance", accountService.getBalance(accountNo));
        return "success";
    }
}

Because this controller doesn’t deal with the Servlet API, testing it is easy. You can test it just like a simple Java class.

package com.apress.springrecipes.bank.web;

import static org.junit.Assert.*;

import com.apress.springrecipes.bank.AccountService;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.ui.ModelMap;


public class DepositControllerTests {

    private static final String TEST_ACCOUNT_NO = "1234";
    private static final double TEST_AMOUNT = 50;
    private AccountService accountService;
    private DepositController depositController;


    @Before
    public void init() {
        accountService = Mockito.mock(AccountService.class);
        depositController = new DepositController(accountService);
    }


    @Test
    public void deposit() {
        //Setup
        Mockito.when(accountService.getBalance(TEST_ACCOUNT_NO)).thenReturn(150.0);
        ModelMap model = new ModelMap();


        //Execute
        String viewName =
            depositController.deposit(TEST_ACCOUNT_NO, TEST_AMOUNT, model);


        assertEquals(viewName, "success");
        assertEquals(model.get("accountNo"), TEST_ACCOUNT_NO);
        assertEquals(model.get("balance"), 150.0);
    }
}

16-4. Manage Application Contexts in Integration Tests

Problem

When creating integration tests for a Spring application, you have to access beans declared in the application context. Without Spring’s testing support, you have to load the application context manually in an initialization method of your tests, a method with @Before or @BeforeClass in JUnit. However, as an initialization method is called before each test method or test class, the same application context may be reloaded many times. In a large application with many beans, loading an application context may require a lot of time, which causes your tests to run slowly.

Solution

Spring’s testing support facilities can help you manage the application context for your tests, including loading it from one or more bean configuration files and caching it across multiple test executions. An application context will be cached across all tests within a single JVM, using the configuration file locations as the key. As a result, your tests can run much faster without reloading the same application context many times.

The TestContext framework provides a few test execution listeners that are registered by default, as shown in Table 16-1.

Table 16-1. Default Test Execution Listeners

TestExecutionListener

Description

DependencyInjectionTestExecutionListener

This injects dependencies, including the managed application context, into your tests.

DirtiesContextTestExecutionListener, DirtiesContextBeforeModesTestExecutionListener

This handles the @DirtiesContext annotation and reloads the application context when necessary.

TransactionalTestExecutionListener

This handles the @Transactional annotation in test cases and does a rollback at the end of a test.

SqlScriptsTestExecutionListener

This detects @Sql annotations on the test and executes the SQL before the start of the test.

ServletTestExecutionListener

This handles the loading of a web application context when the @WebAppConfiguration annotation is detected.

To have the TestContext framework manage the application context, your test class has to integrate with a test context manager internally. For your convenience, the TestContext framework provides support classes that do this, as shown in Table 16-2. These classes integrate with a test context manager and implement the ApplicationContextAware interface, so they can provide access to the managed application context through the protected field applicationContext.

Table 16-2. TestContext Support Classes for Context Management

Testing Framework

TestContext Support Class

JUnit

AbstractJUnit4SpringContextTests

TestNG

AbstractTestNGSpringContextTests

Your test class can simply extend the corresponding TestContext support class for your testing framework.

These TestContext support classes have only DependencyInjectionTestExecutionListener, DirtiesContextTestExecutionListener, and ServletTestExecutionListener enabled.

If you are using JUnit or TestNG , you can integrate your test class with a test context manager by yourself and implement the ApplicationContextAware interface directly, without extending a TestContext support class. In this way, your test class doesn’t bind to the TestContext framework class hierarchy, so you can extend your own base class. In JUnit, you can simply run your test with the test runner SpringRunner to have a test context manager integrated. However, in TestNG, you have to integrate with a test context manager manually.

How It Works

First, let’s declare an AccountService instance and an AccountDao instance in the configuration class. Later, you will create integration tests for them.

package com.apress.springrecipes.bank.config;

import com.apress.springrecipes.bank.AccountServiceImpl;
import com.apress.springrecipes.bank.InMemoryAccountDao;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class BankConfiguration {


    @Bean
    public InMemoryAccountDao accountDao() {
        return new InMemoryAccountDao();
    }


    @Bean
    public AccountServiceImpl accountService() {
        return new AccountServiceImpl(accountDao());
    }
}

Access the Context with the TestContext Framework in JUnit

If you are using JUnit to create tests with the TestContext framework, you will have two options to access the managed application context. The first option is by implementing the ApplicationContextAware interface or using @Autowired on a field of the ApplicationContext type. For this option, you have to explicitly specify a Spring-specific test runner for running your test SpringRunner. You can specify this in the @RunWith annotation at the class level.

package com.apress.springrecipes.bank;

import com.apress.springrecipes.bank.config.BankConfiguration;
import org.junit.After;
import org.junit.Before;
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.junit4.SpringRunner;


import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceJUnit4ContextTests implements ApplicationContextAware {


    private static final String TEST_ACCOUNT_NO = "1234";
    private ApplicationContext applicationContext;
    private AccountService accountService;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }


    @Before
    public void init() {
        accountService = applicationContext.getBean(AccountService.class);
        accountService.createAccount(TEST_ACCOUNT_NO);
        accountService.deposit(TEST_ACCOUNT_NO, 100);
    }


    @Test
    public void deposit() {
        accountService.deposit(TEST_ACCOUNT_NO, 50);
        assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 150, 0);
    }


    @Test
    public void withDraw() {
        accountService.withdraw(TEST_ACCOUNT_NO, 50);
        assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 50, 0);
    }


    @After
    public void cleanup() {
        accountService.removeAccount(TEST_ACCOUNT_NO);
    }


}

You can specify the configuration classes in the classes attribute of the @ContextConfiguration annotation at the class level. When using XML-based configuration, you can use the locations attribute instead. If you don’t specify any test configuration, the TestContext will try to detect one. It will first try to load a file by joining the test class name with -context.xml as the suffix (i.e., AccountServiceJUnit4Tests-context.xml) from the same package as the test class. Next it will scan the test class for any public static inner classes that are annotated with @Configuration. If a file or classes are detected, those will be used to load the test configuration.

By default, the application context will be cached and reused for each test method, but if you want it to be reloaded after a particular test method, you can annotate the test method with the @DirtiesContext annotation so that the application context will be reloaded for the next test method.

The second option to access the managed application context is by extending the TestContext support class specific to JUnit : AbstractJUnit4SpringContextTests. This class implements the ApplicationContextAware interface, so you can extend it to get access to the managed application context via the protected field applicationContext. However, you first have to delete the private field applicationContext and its setter method. Note that if you extend this support class, you don’t need to specify SpringRunner in the @RunWith annotation because this annotation is inherited from the parent.

package com.apress.springrecipes.bank;
...
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;


@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceJUnit4ContextTests extends AbstractJUnit4SpringContextTests {


    private static final String TEST_ACCOUNT_NO = "1234";
    private AccountService accountService;


    @Before
    public void init() {
        accountService = applicationContext.getBean(AccountService.class);
        accountService.createAccount(TEST_ACCOUNT_NO);
        accountService.deposit(TEST_ACCOUNT_NO, 100);
    }
    ...
}

Access the Context with the TestContext Framework in TestNG

To access the managed application context with the TestContext framework in TestNG , you can extend the TestContext support class AbstractTestNGSpringContextTests. This class also implements the ApplicationContextAware interface.

package com.apress.springrecipes.bank;

import com.apress.springrecipes.bank.config.BankConfiguration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;


import static org.testng.Assert.assertEquals;

@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceTestNGContextTests extends AbstractTestNGSpringContextTests {


    private static final String TEST_ACCOUNT_NO = "1234";
    private AccountService accountService;


    @BeforeMethod
    public void init() {
        accountService = applicationContext.getBean(AccountService.class);
        accountService.createAccount(TEST_ACCOUNT_NO);
        accountService.deposit(TEST_ACCOUNT_NO, 100);
    }


    @Test
    public void deposit() {
        accountService.deposit(TEST_ACCOUNT_NO, 50);
        assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 150, 0);
    }


    @Test
    public void withDraw() {
        accountService.withdraw(TEST_ACCOUNT_NO, 50);
        assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 50, 0);
    }


    @AfterMethod
    public void cleanup() {
        accountService.removeAccount(TEST_ACCOUNT_NO);
    }


}

If you don’t want your TestNG test class to extend a TestContext support class, you can implement the ApplicationContextAware interface just as you did for JUnit . However, you have to integrate with a test context manager by yourself. Please refer to the source code of AbstractTestNGSpringContextTests for details.

16-5. Inject Test Fixtures into Integration Tests

Problem

The test fixtures of an integration test for a Spring application are mostly beans declared in the application context. You might want to have the test fixtures automatically injected by Spring via dependency injection, which saves you the trouble of retrieving them from the application context manually.

Solution

Spring’s testing support facilities can inject beans automatically from the managed application context into your tests as test fixtures. You can simply annotate a setter method or field of your test with Spring’s @Autowired annotation or JSR-250’s @Resource annotation to have a fixture injected automatically. For @Autowired, the fixture will be injected by type, and for @Resource, it will be injected by name.

How It Works

You will explore how to inject test fixtures with JUnit and TestNG.

Inject Test Fixtures with the TestContext Framework in JUnit

When using the TestContext framework to create tests, you can have their test fixtures injected from the managed application context by annotating a field or setter method with the @Autowired or @Resource annotation. In JUnit, you can specify SpringRunner as your test runner without extending a support class.

package com.apress.springrecipes.bank;
...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceJUnit4ContextTests {


    private static final String TEST_ACCOUNT_NO = "1234";

    @Autowired
    private AccountService accountService;


    @Before
    public void init() {
        accountService.createAccount(TEST_ACCOUNT_NO);
        accountService.deposit(TEST_ACCOUNT_NO, 100);
    }
    ...
}

If you annotate a field or setter method of a test with @Autowired, it will be injected using autowiring by type. You can further specify a candidate bean for autowiring by providing its name in the @Qualifier annotation. However, if you want a field or setter method to be autowired by name, you can annotate it with @Resource.

By extending the TestContext support class AbstractJUnit4SpringContextTests, you can also have test fixtures injected from the managed application context. In this case, you don’t need to specify SpringRunner for your test, as it is inherited from the parent.

package com.apress.springrecipes.bank;
...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;


@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceJUnit4ContextTests extends AbstractJUnit4SpringContextTests {


    private static final String TEST_ACCOUNT_NO = "1234";

    @Autowired
    private AccountService accountService;
    ...
}

Inject Test Fixtures with the TestContext Framework in TestNG

In TestNG, you can extend the TestContext support class AbstractTestNGSpringContextTests to have test fixtures injected from the managed application context.

package com.apress.springrecipes.bank;
...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;


@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceTestNGContextTests extends AbstractTestNGSpringContextTests {


    private static final String TEST_ACCOUNT_NO = "1234";

    @Autowired
    private AccountService accountService;


    @BeforeMethod
    public void init() {
        accountService.createAccount(TEST_ACCOUNT_NO);
        accountService.deposit(TEST_ACCOUNT_NO, 100);
    }
    ...
}

16-6. Manage Transactions in Integration Tests

Problem

When creating integration tests for an application that accesses a database, you usually prepare the test data in the initialization method. After each test method runs, it may have modified the data in the database. So, you have to clean up the database to ensure that the next test method will run from a consistent state. As a result, you have to develop many database cleanup tasks .

Solution

Spring’s testing support facilities can create and roll back a transaction for each test method, so the changes you make in a test method won’t affect the next one. This can also save you the trouble of developing cleanup tasks to clean up the database.

The TestContext framework provides a test execution listener related to transaction management. It will be registered with a test context manager by default if you don’t specify your own explicitly.

TransactionalTestExecutionListener handles the @Transactional annotation at the class or method level and has the methods run within transactions automatically.

Your test class can extend the corresponding TestContext support class for your testing framework, as shown in Table 16-3, to have its test methods run within transactions. These classes integrate with a test context manager and have @Transactional enabled at the class level. Note that a transaction manager is also required in the bean configuration file.

Table 16-3. TestContext Support Classes for Transaction Management

Testing Framework

TestContext Support Class*

JUnit

AbstractTransactionalJUnit4SpringContextTests

TestNG

AbstractTransactionalTestNGSpringContextTests

These TestContext support classes have TransactionalTestExecutionListener and SqlScriptsTestExecutionListener enabled in addition to DependencyInjectionTestExecutionListener and DirtiesContextTestExecutionListener.

In JUnit and TestNG, you can simply annotate @Transactional at the class level or the method level to have the test methods run within transactions, without extending a TestContext support class. However, to integrate with a test context manager, you have to run the JUnit test with the test runner SpringRunner, and you have to do it manually for a TestNG test.

How It Works

Let’s consider storing your bank system’s accounts in a relational database . You can choose any JDBC-compliant database engine that supports transactions and then execute the following SQL statement on it to create the ACCOUNT table. For testing we are going to use an in-memory H2 database.

CREATE TABLE ACCOUNT (
    ACCOUNT_NO    VARCHAR(10)    NOT NULL,
    BALANCE       DOUBLE         NOT NULL,
    PRIMARY KEY (ACCOUNT_NO)
);

Next, you create a new DAO implementation that uses JDBC to access the database. You can take advantage of JdbcTemplate to simplify your operations.

package com.apress.springrecipes.bank;

import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class JdbcAccountDao extends JdbcDaoSupport implements AccountDao {

    public void createAccount(Account account) {
        String sql = "INSERT INTO ACCOUNT (ACCOUNT_NO, BALANCE) VALUES (?, ?)";
        getJdbcTemplate().update(
            sql, account.getAccountNo(), account.getBalance());
    }


    public void updateAccount(Account account) {
        String sql = "UPDATE ACCOUNT SET BALANCE = ? WHERE ACCOUNT_NO = ?";
        getJdbcTemplate().update(
            sql, account.getBalance(), account.getAccountNo());
    }


    public void removeAccount(Account account) {
        String sql = "DELETE FROM ACCOUNT WHERE ACCOUNT_NO = ?";
        getJdbcTemplate().update(sql, account.getAccountNo());
    }


    public Account findAccount(String accountNo) {
        String sql = "SELECT BALANCE FROM ACCOUNT WHERE ACCOUNT_NO = ?";
        double balance =
            getJdbcTemplate().queryForObject(sql, Double.class, accountNo);
        return new Account(accountNo, balance);
    }
}

Before you create integration tests to test the AccountService instance that uses this DAO to persist account objects, you have to replace InMemoryAccountDao with this DAO in the configuration class and configure the target data source as well.

Note

To use H2, you have to add it as a dependency to your classpath.

<dependency>
    <groupId>com.h2database:</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.194</version>
</dependency>

Or when using Gradle, add the following:

testCompile 'com.h2database:h2:1.4.194'
@Configuration
public class BankConfiguration {


    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:h2:mem:bank-testing");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        return dataSource;
    }


    @Bean
    public AccountDao accountDao() {
        JdbcAccountDao accountDao = new JdbcAccountDao();
        accountDao.setDataSource(dataSource());
        return accountDao;
    }


    @Bean
    public AccountService accountService() {
        return new AccountServiceImpl(accountDao());
    }
}

Manage Transactions with the TestContext Framework in JUnit

When using the TestContext framework to create tests , you can have the tests methods run within transactions by annotating @Transactional at the class or method level. In JUnit, you can specify SpringRunner for your test class so that it doesn’t need to extend a support class.

package com.apress.springrecipes.bank;
...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;


@RunWith(SpringRunner.class)
@ContextConfiguration(classes = BankConfiguration.class)
@Transactional
public class AccountServiceJUnit4ContextTests {


    private static final String TEST_ACCOUNT_NO = "1234";

    @Autowired
    private AccountService accountService;


    @Before
    public void init() {
        accountService.createAccount(TEST_ACCOUNT_NO);
        accountService.deposit(TEST_ACCOUNT_NO, 100);
    }


    // Don't need cleanup() anymore
    ...
}

If you annotate a test class with @Transactional, all of its test methods will run within transactions. An alternative is to annotate individual methods with @Transactional, not the entire class.

By default, transactions for test methods will be rolled back at the end. You can alter this behavior by disabling the defaultRollback attribute of @TransactionConfiguration, which should be applied to the class level. Also, you can override this class-level rollback behavior at the method level with the @Rollback annotation, which requires a Boolean value.

Note

Methods with the @Before or @After annotation will be executed within the same transactions as test methods. If you have methods that need to perform initialization or cleanup tasks before or after a transaction, you have to annotate them with @BeforeTransaction or @AfterTransaction.

Finally, you also need a transaction manager configured in the bean configuration file. By default, a bean whose type is PlatformTransactionManager will be used, but you can specify another one in the transactionManager attribute of the @TransactionConfiguration annotation by giving its name.

@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

In JUnit, an alternative to managing transactions for test methods is to extend the transactional TestContext support class AbstractTransactionalJUnit4SpringContextTests, which has @Transactional enabled at the class level so that you don’t need to enable it again. By extending this support class, you don’t need to specify SpringRunner for your test, as it is inherited from the parent.

package com.apress.springrecipes.bank;
...
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;


@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceJUnit4ContextTests extends AbstractTransactionalJUnit4SpringContextTests {
    ...
}

Manage Transactions with the TestContext Framework in TestNG

To create TestNG tests that run within transactions, your test class can extend the TestContext support class AbstractTransactionalTestNGSpringContextTests to have its methods run within transactions.

package com.apress.springrecipes.bank;
...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;


@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceTestNGContextTests extends
    AbstractTransactionalTestNGSpringContextTests {


    private static final String TEST_ACCOUNT_NO = "1234";

    @Autowired
    private AccountService accountService;


    @BeforeMethod
    public void init() {
        accountService.createAccount(TEST_ACCOUNT_NO);
        accountService.deposit(TEST_ACCOUNT_NO, 100);
    }


    // Don't need cleanup() anymore
    ...
}

16-7. Access a Database in Integration Tests

Problem

When creating integration tests for an application that accesses a database, especially one developed with an ORM framework, you might want to access the database directly to prepare test data and validate the data after a test method runs.

Solution

Spring’s testing support facilities can create and provide a JDBC template for you to perform database-related tasks in your tests. Your test class can extend one of the transactional TestContext support classes to access the precreated JdbcTemplate instance. These classes also require a data source and a transaction manager in the bean configuration file.

How It Works

When using the TestContext framework to create tests , you can extend the corresponding TestContext support class to use a JdbcTemplate instance via a protected field. For JUnit, this class is AbstractTransactionalJUnit4SpringContextTests, which provides similar convenient methods for you to count the number of rows in a table, delete rows from a table, and execute a SQL script.

package com.apress.springrecipes.bank;
...
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;


@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceJUnit4ContextTests extends AbstractTransactionalJUnit4SpringContextTests {
    ...
    @Before
    public void init() {
        executeSqlScript("classpath:/bank.sql",true);
        jdbcTemplate.update(
            "INSERT INTO ACCOUNT (ACCOUNT_NO, BALANCE) VALUES (?, ?)",
            TEST_ACCOUNT_NO, 100);
    }


    @Test
    public void deposit() {
        accountService.deposit(TEST_ACCOUNT_NO, 50);
        double balance = jdbcTemplate.queryForObject(
            "SELECT BALANCE FROM ACCOUNT WHERE ACCOUNT_NO = ?",
            Double.class, TEST_ACCOUNT_NO);
        assertEquals(balance, 150.0, 0);
    }


    @Test
    public void withDraw() {
        accountService.withdraw(TEST_ACCOUNT_NO, 50);
        double balance = jdbcTemplate.queryForObject(
            "SELECT BALANCE FROM ACCOUNT WHERE ACCOUNT_NO = ?",
            Double.class, TEST_ACCOUNT_NO);
        assertEquals(balance, 50.0, 0);
    }
}

Instead of using the executeSqlScript method , you could also put the @Sql annotation on the class or test method to execute some SQL or a script.

@ContextConfiguration(classes = BankConfiguration.class)
@Sql(scripts="classpath:/bank.sql")
public class AccountServiceJUnit4ContextTests extends AbstractTransactionalJUnit4SpringContextTests {


    private static final String TEST_ACCOUNT_NO = "1234";

    @Autowired
    private AccountService accountService;


    @Before
    public void init() {
        jdbcTemplate.update(
            "INSERT INTO ACCOUNT (ACCOUNT_NO, BALANCE) VALUES (?, ?)",
            TEST_ACCOUNT_NO, 100);
    }
}

With the @Sql method, you can execute scripts which you can specify in the scripts attribute or put in SQL statements directly in the statements attribute of the annotation. Finally, you can specify when to execute the specified instructions before or after a test method. You can put multiple @Sql annotations on a class/method so you can execute statements before and after the test.

In TestNG, you can extend AbstractTransactionalTestNGSpringContextTests to use a JdbcTemplate instance.

package com.apress.springrecipes.bank;
...
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;


@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceTestNGContextTests extends AbstractTransactionalTestNGSpringContextTests {
    ...
    @BeforeMethod
    public void init() {
        executeSqlScript("classpath:/bank.sql",true);
        jdbcTemplate.update(
            "INSERT INTO ACCOUNT (ACCOUNT_NO, BALANCE) VALUES (?, ?)",
            TEST_ACCOUNT_NO, 100);
    }


    @Test
    public void deposit() {
        accountService.deposit(TEST_ACCOUNT_NO, 50);
        double balance = jdbcTemplate.queryForObject(
            "SELECT BALANCE FROM ACCOUNT WHERE ACCOUNT_NO = ?",
            Double.class, TEST_ACCOUNT_NO);
        assertEquals(balance, 150, 0);
    }


    @Test
    public void withDraw() {
        accountService.withdraw(TEST_ACCOUNT_NO, 50);
        double balance = jdbcTemplate.queryForObject(
            "SELECT BALANCE FROM ACCOUNT WHERE ACCOUNT_NO = ?",
            Double.class, TEST_ACCOUNT_NO);
        assertEquals(balance, 50, 0);
    }
}

16-8. Use Spring’s Common Testing Annotations

Problem

You often have to manually implement common testing tasks, such as expecting an exception to be thrown, repeating a test method multiple times, ensuring that a test method will complete in a particular time period, and so on.

Solution

Spring’s testing support provides a common set of testing annotations to simplify your test creation. These annotations are Spring-specific but independent of the underlying testing framework. Of these, the annotations in Table 16-4 are useful for common testing tasks. However, they are supported only for use with JUnit.

Table 16-4. Spring’s Test Annotations

Annotation

Description

@Repeat

This indicates that a test method has to run multiple times. The number of times it will run is specified as the annotation value.

@Timed

This indicates that a test method must complete in a specified time period (in milliseconds). Otherwise, the test fails. Note that the time period includes the repetitions of the test method and any initialization and cleanup methods.

@IfProfileValue

This indicates that a test method can run only in a specific testing environment. This test method will run only when the actual profile value matches the specified one. You can also specify multiple values so that the test method will run if any of the values is matched. By default, SystemProfileValueSource is used to retrieve system properties as profile values, but you can create your own ProfileValueSource implementation and specify it in the @ProfileValueSourceConfiguration annotation.

You can use Spring’s testing annotations by extending one of the TestContext support classes. If you don’t extend a support class but run your JUnit test with the test runner SpringRunner, you can also use these annotations.

How It Works

When using the TestContext framework to create tests for JUnit, you can use Spring’s testing annotations if you run your test with SpringRunner or extend a JUnit TestContext support class.

package com.apress.springrecipes.bank;
...
import org.springframework.test.annotation.Repeat;
import org.springframework.test.annotation.Timed;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;


@ContextConfiguration(locations = "/beans.xml")
public class AccountServiceJUnit4ContextTests extends AbstractTransactionalJUnit4SpringContextTests {
    ...
    @Test
    @Timed(millis = 1000)
    public void deposit() {
        ...
    }


    @Test
    @Repeat(5)
    public void withDraw() {
        ...
    }
}

16-9. Implement Integration Tests for Spring MVC Controllers

Problem

In a web application, you want to integration test the web controllers developed with the Spring MVC framework.

Solution

A Spring MVC controller is invoked by DispatcherServlet with an HTTP request object and an HTTP response object. After processing a request, the controller returns it to DispatcherServlet for rendering the view. The main challenge of integration testing Spring MVC controllers, as well as web controllers in other web application frameworks, is simulating HTTP request objects and response objects in a unit testing environment as well as setting up the mocked environment for a unit test. Fortunately, Spring has the mock MVC part of the Spring Test support. This allows for easy setup of a mocked servlet environment.

Spring Test Mock MVC will set up a WebApplicationContext according to your configuration. Next you can use the MockMvc API to simulate HTTP requests and verify the result.

How It Works

In the banking application, you want to integration test your DepositController. Before you can start testing, you need to create a configuration class to configure the web-related beans.

package com.apress.springrecipes.bank.web.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.view.InternalResourceViewResolver;


@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.apress.springrecipes.bank.web")
public class BankWebConfiguration {


    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;


    }
}

The configuration enables annotation-based controllers by using the @EnableWebMvc annotation; next you want the @Controller annotated beans to be picked up automatically using the @ComponentScan annotation. Finally, there is an InternalResourceViewResolver that turns the name of the view into a URL, which normally would be rendered by the browser, that you will now validate in the controller.

Now that the web-based configuration is in place, you can start to create your integration test. This unit test has to load your BankWebConfiguration class and also has to be annotated with @WebAppConfiguration to inform the TestContext framework you want a WebApplicationContext instead of a regular ApplicationContext.

Integration Test Spring MVC Controllers with JUnit

In JUnit it is the easiest to extend one of the base classes, in this case AbstractTransactionalJUnit4SpringContextTests because you want to insert some test data and to roll back after the tests complete.

package com.apress.springrecipes.bank.web;

import com.apress.springrecipes.bank.config.BankConfiguration;
import com.apress.springrecipes.bank.web.config.BankWebConfiguration;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
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.web.context.WebApplicationContext;


import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@ContextConfiguration(classes= { BankWebConfiguration.class, BankConfiguration.class})
@WebAppConfiguration
public class DepositControllerJUnit4ContextTests extends AbstractTransactionalJUnit4SpringContextTests {


    private static final String ACCOUNT_PARAM = "accountNo";
    private static final String AMOUNT_PARAM = "amount";


    private static final String TEST_ACCOUNT_NO = "1234";
    private static final String TEST_AMOUNT = "50.0";


    @Autowired
    private WebApplicationContext webApplicationContext;


    private MockMvc mockMvc;

    @Before
    public void init() {
        executeSqlScript("classpath:/bank.sql", true);
        jdbcTemplate.update(
            "INSERT INTO ACCOUNT (ACCOUNT_NO, BALANCE) VALUES (?, ?)",
            TEST_ACCOUNT_NO, 100);
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();


    }

    @Test
    public void deposit() throws Exception {
        mockMvc.perform(
            get("/deposit.do")
                .param(ACCOUNT_PARAM, TEST_ACCOUNT_NO)
                .param(AMOUNT_PARAM, TEST_AMOUNT))
            .andDo(print())
            .andExpect(forwardedUrl("/WEB-INF/views/success.jsp"))
            .andExpect(status().isOk());
    }
  }

In the init method , you prepare the MockMvc object by using the convenient MockMvcBuilders. Using the factory method webAppContextSetup, you can use the already loaded WebApplicationContext to initialize the MockMvc object. The MockMvc object basically mimics the behavior of DispatcherServlet, which you would use in a Spring MVC–based application. It will use the passed-in WebApplicationContext to configure the handler mappings and view resolution strategies and will also apply any interceptors that are configured.

There is also some setup of a test account so that you have something to work with.

In the deposit test method, the initialized MockMvc object is used to simulate an incoming request to the /deposit.do URL with two request parameters, accountNo and amount. The MockMvcRequestBuilders.get factory method results in a RequestBuilder instance that is passed to the MockMvc.perform method.

The perform method returns a ResultActions object that can be used to do assertions and certain actions on the return result. The test method prints the information for the created request and returned response using andDo(print()), which can be useful while debugging your test. Finally, there are two assertions to verify that everything works as expected. The DepositController returns success as the viewname, which should lead to a forward to /WEB-INF/views/success.jsp because of the configuration of the ViewResolver. The return code of the request should be 200 (OK), which can be tested with status().isOk() or status().is(200).

Integration Test Spring MVC Controllers with TestNG

Spring Mock MVC can also be used with TestNG to extend the appropriate base class AbstractTransactionalTestNGSpringContextTests and add the @WebAppConfiguration annotation.

@ContextConfiguration(classes= { BankWebConfiguration.class, BankConfiguration.class})
@WebAppConfiguration
public class DepositControllerTestNGContextTests
    extends AbstractTransactionalTestNGSpringContextTests {


    @BeforeMethod
    public void init() {
        executeSqlScript("classpath:/bank.sql", true);
        jdbcTemplate.update(
            "INSERT INTO ACCOUNT (ACCOUNT_NO, BALANCE) VALUES (?, ?)",
            TEST_ACCOUNT_NO, 100);
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }


}

16-10. Write Integration Tests for REST Clients

Problem

You want to write an integration test for a RestTemplate-based client.

Solution

When writing an integration test for a REST-based client, you don’t want to rely on the availability of the external service. You can write an integration test using a mock server to return an expected result instead of calling the real endpoint.

How It Works

When working at a bank, you need to validate the account numbers people enter; you could implement your own validation or you could reuse an existing one. You are going to implement an IBAN validation service that will use the API available at http://openiban.com .

First you write an interface defining the contract.

package com.apress.springrecipes.bank.web;

public interface IBANValidationClient {

    IBANValidationResult validate(String iban);
}

IBANValidationResult contains the results of the call to the validation endpoint.

package com.apress.springrecipes.bank.web;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class IBANValidationResult {

    private boolean valid;
    private List<String> messages = new ArrayList<>();
    private String iban;


    private Map<String, String> bankData = new HashMap<>();

    public boolean isValid() {
        return valid;
    }


    public void setValid(boolean valid) {
        this.valid = valid;
    }


    public List<String> getMessages() {
        return messages;
    }


    public void setMessages(List<String> messages) {
        this.messages = messages;
    }


    public String getIban() {
        return iban;
    }


    public void setIban(String iban) {
        this.iban = iban;
    }


    public Map<String, String> getBankData() {
        return bankData;
    }


    public void setBankData(Map<String, String> bankData) {
        this.bankData = bankData;
    }


    @Override
    public String toString() {
        return "IBANValidationResult [" +
               "valid=" + valid +
               ", messages=" + messages +
               ", iban='" + iban + ''' +
               ", bankData=" + bankData +
               ']';
    }
}

Next write the OpenIBANValidationClient class, which will use a RestTemplate instance to communicate with the API. For easy access to a RestTemplate instance, you can extend RestGatewaySupport.

package com.apress.springrecipes.bank.web;

import org.springframework.stereotype.Service;
import org.springframework.web.client.support.RestGatewaySupport;


@Service
public class OpenIBANValidationClient extends RestGatewaySupport implements IBANValidationClient {


    private static final String URL_TEMPLATE = "https://openiban.com/validate/{IBAN_NUMBER}?getBIC=true&validateBankCode=true";

    @Override
    public IBANValidationResult validate(String iban) {


        return getRestTemplate().getForObject(URL_TEMPLATE, IBANValidationResult.class, iban);
    }
}

Next you will create a test that will construct a MockRestServiceServer class for the OpenIBANValidationClient class, and you configure it to return a specific result in JSON for an expected request.

package com.apress.springrecipes.bank.web;

import com.apress.springrecipes.bank.config.BankConfiguration;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;


import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;


@RunWith(SpringRunner.class)
@ContextConfiguration(classes= { BankConfiguration.class})
public class OpenIBANValidationClientTest {


    @Autowired
    private OpenIBANValidationClient client;


    private MockRestServiceServer mockRestServiceServer;

    @Before
    public void init() {
        mockRestServiceServer = MockRestServiceServer.createServer(client);
    }


    @Test
    public void validIban() {


        mockRestServiceServer
            .expect(requestTo("https://openiban.com/validate/NL87TRIO0396451440?getBIC=true&validateBankCode=true"))
                .andRespond(withSuccess(new ClassPathResource("NL87TRIO0396451440-result.json"), MediaType.APPLICATION_JSON));


        IBANValidationResult result = client.validate("NL87TRIO0396451440");
        assertTrue(result.isValid());
    }


    @Test
    public void invalidIban() {


        mockRestServiceServer
            .expect(requestTo("https://openiban.com/validate/NL28XXXX389242218?getBIC=true&validateBankCode=true"))
                .andRespond(withSuccess(new ClassPathResource("NL28XXXX389242218-result.json"), MediaType.APPLICATION_JSON));


        IBANValidationResult result = client.validate("NL28XXXX389242218");
        assertFalse(result.isValid());
    }
}

The test class has two test methods , and both are quite similar. In the init method, you create a MockRestServiceService class using the OpenIBANValidationClient class (this is possible because it extends RestGatewaySupport; if that wasn’t the case, you would have to use the configured RestTemplate class to create a mocked server). In the test method, you set up the expectation with a URL, and now when that URL is called, a JSON response, from the classpath, will be returned as the answer.

For testing you probably want to use some well-known responses from the server, and for this you could use some recorded results from a live system or maybe they already provide results for testing.

Summary

In this chapter, you learned about the basic concepts and techniques used in testing Java applications. JUnit and TestNG are the most popular testing frameworks on the Java platform. Unit tests are used for testing a single programming unit, which is typically a class or a method in object-oriented languages. When testing a unit that depends on other units, you can use stubs and mock objects to simulate its dependencies, thus making the tests simpler. In contrast, integration tests are used to test several units as a whole.

In the web layer, controllers are usually hard to test. Spring offers mock objects for the Servlet API so that you can easily simulate web request and response objects to test a web controller. There is also Spring Mock MVC for easy integration testing of your controllers. What applies to controllers also applies to REST-based clients. To help you test these clients, Spring provides the MockRestServiceServer, which you can use to mock an external system.

Spring’s testing support facilities can manage application contexts for your tests by loading them from bean configuration files and caching them across multiple test executions. You can access the managed application context in your tests, as well as have your test fixtures injected from the application context automatically. In addition, if your tests involve database updates, Spring can manage transactions for them so that changes made in one test method will be rolled back and thus won’t affect the next test method. Spring can also create a JDBC template for you to prepare and validate your test data in the database.

Spring provides a common set of testing annotations to simplify your test creation. These annotations are Spring-specific but independent of the underlying testing framework. However, some of these are only supported for use with JUnit.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset