automation

Integration Testing Using Cucumber-Java

Cucumber executes test script defined in the feature file which itself is written Gherkin. Gherkin is simple English language which has different steps like Given, When, Then, Scenario etc. and this has become of the most widely used test scripting language in distributed systems like microservices to test APIs.

Learning by Doing

I have an API UserApi built with Spring Boot as a server running on port 8080. To test the endpoints of my API, I have created the BBD testing project UserApiTesting using Cucumber-Java as client side.

In UserApiTesting, Users.feature has multiple scenarios. Among them Get All Users is one. Here, the Given condition is two users that already exist in the system. We need to invoke getAllUsers which itself is in When glue step and  validate the response from the service which is Then glue step.

Users.feature
@api
Feature: Users
  Scenario: Get All Users
  Find All Users
    Given Two users should exist in the system
    When getAllUsers service is invoked
    Then "2" users should exist

In testing module, ApiTestRunner.java is the main runner file which picks the scenarios that has @api tag and ignored those scenario that has @ignored tag. After the completion of the test, the report is generated under target/reports/cucumber and those scenarios which creates the data for Given steps will be teared down using tearDownUsers().

ApiTestRunner.java
@RunWith(Cucumber.class)
@CucumberOptions(
        format = {
                "html:target/cucumber/api",
                "json:target/reports/cucumber/api.json"
        },
        tags = {"~@ignored", "@api"},
        features = "classpath:feature",
        glue = {"classpath:glue"}
)
public class ApiTestRunner {

    @AfterClass
    public static void dropDown() {
        //Runs only once after all suite
        ApplicationProperties properties = new ApplicationProperties();
        UserApiResource userApiResource = new UserApiResource(properties);
        DropUsers dropUsers = new DropUsers(userApiResource);
        dropUsers.tearDownUsers();
    }

You can prepare data required for the tests with @Given which is the first glue step that is picked when the application bootstraps. In example below, two users are created once UserApi bootstraps. The regex inside @Given must matched the Given step in Get All Users in Users.feature.

Users.java
 @Given("^Two users should exist in the system$")
 public void two_users_should_exist_in_the_system() throws Throwable {
        // Two users exist in http://localhost:8080 so intentionally left balnk
 }

 

UserApiResource.java has the client code to call actual resource in UserApi. This has actual service class that is under test. Here also regex should be matched.

UsersServiceCall.java    

   @Getter
    @NonNull
    private UserApiResource resource;
    @Getter
    private List<UserDto> users;

    @When("^getAllUsers service is invoked$")
    public void users_service_is_invoked() throws Throwable {
        log.info("UsersServiceCall.users_service_is_invoked()");
        this.users = this.resource.getUsers("/users", null);
    }

Here the actual resource call is happening using RestTemplate. POST is called depending on the condition, however, GET is called always through out the When conditions. The response from the resouce is mapped into List<UserDto> and returned back to caller i.e When glue step.

UserApiResource.java
 public List<UserDto> getUsers(String path, String userId) {
        String resourceUrl = this.resourceUrl + path;
        try {
            RestTemplate restTemplate = RestHelper.getRestTemplate();
            HttpHeaders headers = RestHelper.getHeaders();
            HttpEntity entity = new HttpEntity(headers);

            List<UserDto> userDtos = new ArrayList<>();
            if (userId != null) {
                /*UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(resourceUrl)
                        .queryParam("userId", userId);
                resourceUrl = builder.toUriString();*/
                resourceUrl = resourceUrl +"/"+ Long.valueOf(userId);
                HttpEntity<UserDto> usersTemp = restTemplate.exchange(resourceUrl, HttpMethod.GET, entity, UserDto.class);
                UserDto userDto = usersTemp.getBody();
                userDtos.add(userDto);
            } else {
                HttpEntity<List<UserDto>> usersTemp = restTemplate.exchange(resourceUrl, HttpMethod.GET, entity, new             ParameterizedTypeReference<List<UserDto>>() {
                });
                userDtos = usersTemp.getBody();
            }
            return userDtos;
        } catch (Exception e) {
            log.error("Error occured in UserApiResource.getUsers", e.getMessage());
            throw new RuntimeException("Exception in UserApiResource.getUsers", e);
        }
    }

Now, finally this is how the actual validation class looks like. Once When glue step called, then Then glue step is called. Here in our case UsersServiceValidation.java class has Then condition in which UsersServiceCall object is injected which contains the response from the resource. In Then step expectedNumberOfUsers is injecting from feature file.


Finally validation is done with the help of JUnit as:

UsersServiceValidation.java
    @NonNull
    private UsersServiceCall usersServiceCall;

    @Then("^\"([^\"]*)\" users should exist$")
    public void users_should_exist(String expectedNumberOfUsers) throws Throwable {
        assertNotNull(this.usersServiceCall);
        assertNotNull(this.usersServiceCall.getUsers());
        assertEquals(expectedNumberOfUsers, String.valueOf(this.usersServiceCall.getUsers().size()));
    }

Conclusion

You can see how easy it is to make a call and validate the response of the restful API. Once the test runs successfully, you can see the generated report which is very easy to understand for both analysts and business.

​​​All the source code is available on GitHub.

Implementing Swagger2 in Spring Boot Application for API Documentation
Creating Spring Boot Application for WebLogic and Tomcat