This article goes beyond regular unit testing and describes various approaches for constructing spring boot integration tests. It explores configuration options and relies on @SpringBootTest annotation to construct rest controller integration test classes. We’ll look at tests constructed with the help of Spring’s TestRestTemplate as well as RestAssured library.
Take a look at allAroundJava github for more test examples.

Goals of integration tests

While with unit tests we attempt to get a quick answer regarding the workings of a single function, with integration tests we’re looking at a wider scope. Integration tests attempt to verify how our application components, that is services, repositories, controllers work together, combined. Since we’re using Spring IoC container, it seems really important to see if all the beans are correctly wired together and working as expected.
Integration tests sometimes go beyond a single application and explore how various apps communicate together.
The rest controller integration tests which we will explore, are an extension to our previous article on RestController Unit Testing. We will focus on making REST calls against controllers working in an embedded web server with all our application layers wired together and none of them mocked.

Typical test scenarios

Coming back to allAroundJava’s medical clinic example, we will be covering the following end to end scenarios, which can be described as user stories with acceptance criteria:

  • As an API user I can create a new doctor so that he can take visits
    • Each doctor is identified with unique Id.
  • As an api user I can create an appointment slot for doctor so I indicate when a doctor is available to see a patient
    • Appointment slot can only be created for existing doctor
    • Appointment has a start and an end date

Setup

The setup is a fully working application with repository, service and REST Controllers configured almost like it was a real application. Repositories use an in-memory H2 database, re-created with each test run.
Please remember that Spring shares its context across tests with the same setup. As setting up Spring Context together with web server costs a significant amount of time, this is a helpful optimization. It’s worth noting that any changes to objects in Spring Context will be shared across tests.

Spring setup

In order to have access to Spring’s test support, we’ll add a Maven dependency.

Here is a class assisting in Hibernate configuration

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.allaroundjava.dao"})
public class TestJpaConfig {
    @Bean
    LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean emf =
                new LocalContainerEntityManagerFactoryBean();
        emf.setPackagesToScan("com.allaroundjava.model");
        emf.setDataSource(createDataSource());
        emf.setJpaVendorAdapter(createJpaVendorAdapter());
        emf.setJpaProperties(createHibernateProperties());
        emf.afterPropertiesSet();
        return emf;
    }

     protected DataSource createDataSource() {
        EmbeddedDatabaseBuilder builder =
                new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.H2).build();
    }

    private JpaVendorAdapter createJpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

    private Properties createHibernateProperties() {
        Properties properties = new Properties();
        properties.setProperty("javax.persistence.schema-generation.database.action", "drop-and-create");
        properties.setProperty(
                "hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        return properties;
    }

    @Bean
    PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

We’re using an embedded H2 database, everything else works like a standard application config.
Here’s also a necessary Spring auto-configuration.

@Configuration
@EnableAutoConfiguration
public class IntegrationTestConfig {
}	

The Test Class

We build the test class with @SpringBootTest annotation which starts up an Application Context used throughout our test. In the classes property of @SpringBootTest annotation we can specify which configuration classes build our Application Context. By default @SpringBootTest annotation does not provide any web environment.
In order to set up a test web server we need to use @SpringBootTest’s webEnvironment annotation.
There are few modes in which the web server can be started.

  • RANDOM_PORT – this is a recommended option where a real, embedded web server starts on a random port
  • DEFINED_PORT – web server will start on an 8080 or a port defined in application.properties
  • MOCK – loads a mock web environment where enbedded servers are not started up

Here’s our test class configuration

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = {TestJpaConfig.class, AppConfig.class, IntegrationTestConfig.class})
public class BookingAppointmentIntegrationTest {
    @Autowired
    private TestRestTemplate restTemplate;

Using TestRestTemplate

TestRestTemplate is a wrapper to Spring’s RestTemplate class providing convenience methods for asserting responses and simplifying REST testing process. You can entier instantiate it directly in a test class with a new keyword. Alternatively, if your test class uses @SpringBootTest annotation, an instance of TestRestTemplate can be autowired just like in the example above.
Using TestRestTemplate gives us a possibility to make REST calls strongly typed. Here’s an example test using this class.

@Test
public void whenCreateDoctor_thenDoctorCanBeRetrieved() {
    //Given DoctorDto object
    HttpEntity<DoctorDto> requestEntity = getDoctorDtoHttpEntity("Doctor John");

    //When Post created DoctorDto
    ResponseEntity<DoctorDto> postDoctor = restTemplate.exchange("/doctors", HttpMethod.POST, requestEntity, DoctorDto.class);
    Assert.assertTrue(postDoctor.getStatusCode().equals(HttpStatus.CREATED));
    Assert.assertNotNull(postDoctor.getBody().getEntityId());

    //Then Doctor can be retrieved
    ResponseEntity<DoctorDto> getDoctor = restTemplate.getForEntity("/doctors/"+postDoctor.getBody().getEntityId(), DoctorDto.class);
    Assert.assertTrue(getDoctor.getStatusCode().is2xxSuccessful());   
}

Using Rest Assured

If you’d like to use the benefits of Rest Assured library, there’s also a possibility to configure the SpringBoot RestAssured combo. All it really needs is the rest-assured maven dependency and the test can be structured in the following way. This particular test is verifying the creation of Appointment Slot for a particular doctor.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = {TestJpaConfig.class, AppConfig.class, IntegrationTestConfig.class})
public class CreatingAppointmentSlotIntegrationTest {
    @LocalServerPort
    private int port;

    @Before
    public void setUp() {
        RestAssured.port = port;
    }

    @Test
    public void whenCreatingSlotForExistingDoctor_thenSlotIsCreated() {
        DoctorDto doctor = createDoctorDto("Doctor John");
        String doctorId =
            given()
                .contentType(ContentType.XML)
                .body(doctor)
            .when()
                .post("/doctors")
            .then()
                .log().all()
                .statusCode(201)
                .body("doctorDto.entityId", notNullValue())
                .extract().path("doctorDto.entityId");

        AppointmentSlotDto appointmentSlotDto = createAppointmentSlotDto(doctorId);

        given()
            .contentType(ContentType.XML)
            .body(appointmentSlotDto)
        .when()
            .post("/slots")
        .then()
            .log().all()
            .statusCode(201)
            .body("appointmentSlotDto.doctorId", equalTo(doctorId))
            .body("appointmentSlotDto.entityId", notNullValue());
    }
 

The configuration of SpringBoot with RestAssured is rather trivial. All we need to do is to inject the port on which our test server is running and tell RestAssured to use this very port.

Here’s some more useful stuff on REST

Leave a Comment