Test your local and remote REST API with Spring boot, Cucumber and Gherkin !
Made with , Cucumber and Gherkin ! Inspired from the awesome apickli project project.
To test your messaging system with cucumber, gherkin please use this library
Stack
- Spring Boot
- Cucumber / Gherkin
- Jayway JsonPath
Installation
<dependency>
<groupId>fr.redfroggy.test.bdd</groupId>
<artifactId>cucumber-restapi</artifactId>
</dependency>
Run npm install
to add commitlint + husky
Example
Feature: Users api tests
Background:
Given http baseUri is /api/
And I set Accept-Language http header to en-US
And I set http headers to:
| Accept | application/json |
| Content-Type | application/json |
Scenario: Should be authenticated
When I HEAD authenticated
Then http response code should be 401
When I authenticate with login/password tstark/marvel
And I HEAD /authenticated
Then http response code should be 200
And I store the value of http response header Authorization as authToken in scenario scope
Scenario: Add tony stark user
When I authenticate with login/password tstark/marvel
And I set http body to {"id":"1","firstName":"Tony","lastName":"Stark","age":"40", "sessionIds": ["43233333", "45654345"]}
And I set http body path $.age to 42
And I POST /users
Then http response header Content-Type should not be application/xml
And http response code should be 201
And http response header Content-Type should be application/json
And http response body path $.id must be 1
# Should not fail even if relatedTo has null value, will fail only if relatedTo exists and its value is different from 1
And http response body path $.relatedTo should be 1
And http response body path $.firstName should be Tony
And http response body path $.lastName should be Stark
And http response body path $.age should be 42
And http response body path $.sessionIds should be ["43233333", "45654345"]
And http response body path $.sessionIds must not be []
And I store the value of http body path $.id as starkUser in scenario scope
And I store the value of http body path $.sessionIds.[0] as firstSessionId in scenario scope
And http value of scenario variable starkUser should be 1
Scenario: Add bruce wayne user
And I set Authorization http header to `$authToken`
And I set http body to {"id": "2","firstName":"Bruce","lastName":"Wayne","age":"50", "relatedTo": {"id":`$starkUser`}, "sessionIds": [`$firstSessionId`]}
And I POST /users
Then http response header Content-Type should exist
Then http response header xsrf-token should not exist
And http response body should be valid json
Then http response code should not be 404
And http response code should be 201
And http response body path $.id should be 2
And http response body path $.firstName should be Bruce
And http response body path $.lastName should be Wayne
And http response body path $.age should be 50
And http response body path $.relatedTo.id should be 1
And http response body path $.sessionIds should be ["43233333"]
Scenario: Add bruce banner user
And I set Authorization http header to `$authToken`
And I set http body with file fixtures/bruce-banner.user.json
And I POST /users
And http response code should be 201
And http response body path $.id should be 15948349393
And http response body path $.firstName should be Bruce
And http response body path $.lastName should be Banner
And http response body path $.age should be 45
And http response body path $.sessionIds should be ["99869448"]
Scenario: Update tony stark user
When I mock third party api call GET /public/characters/1 with return code 200, content type: application/json and body: {"comicName": "IronMan", "city": "New York", "mainColor": ["red", "yellow"]}
And I set http body to {"id":"1","firstName":"Tony","lastName":"Stark","age":"60"}
And I PUT /users/1
Then http response code should be 200
And http response body path $.age should be 60
When I GET /users/1
Then http response code should be 200
And http response body path $.age should be 60
Scenario: Patch bruce wayne user
When I mock third party api call GET /public/characters/2 with return code 200, content type: application/json and body: {"comicName": "Batman", "city": "Gotham City", "mainColor": ["black"]}
And I set http body to {"lastName":"WAYNE"}
And I PATCH /users/2
Then http response code should be 200
And http response body path $.lastName should be WAYNE
When I GET /users/2
Then http response code should be 200
And http response body path $.lastName should be WAYNE
Scenario: Get users
When I GET /users
And http response body should be valid json
Then http response code should be 200
And http response body is typed as array for path $
And http response body is typed as array using path $ with length 3
And http response body path $.[0].id should be `$starkUser`
And http response body path $.[0].firstName should be Tony
And http response body path $.[0].lastName should not be Wayne
And http response body path $.[1].id should exists
And http response body path $.[4].id should not exist
And http response body should contain Bruce
Scenario: Search for valid users
And I set http query parameter name to wayne
When I GET /users
And http response body should be valid json
Then http response code should be 200
And http response body is typed as array using path $ with length 1
And http response body path $.[0].id should be 2
And http response body path $.[0].firstName should be Bruce
Scenario: Search for invalid users
And I set http query parameter name to djndjndsqds
When I GET /users
And http response body should be valid json
Then http response code should be 200
And http response body is typed as array using path $ with length 0
And http response body path $ should not have content
Scenario: Get user Tony Stark
When I mock third party api call GET /public/characters/1?format=json with return code 200, content type: application/json and body: {"comicName": "IronMan", "city": "New York", "mainColor": ["red", "yellow"]}
And I GET /users/1?format=json
Then http response code should be 200
And http response body path $.id should be 1
And http response body path $.firstName should be Tony
And http response body path $.lastName should be Stark
And http response body path $.age should be 60
And http response body path $.details.comicName should be IronMan
And I store the value of http response header Content-Type as httpContentType in scenario scope
And http value of scenario variable httpContentType should be application/json
And http response body should contain `$firstSessionId`
Scenario: Get user Bruce Wayne
When I mock third party api call GET /public/characters/2 with return code 200, content type: application/json and file: fixtures/bruce_wayne_marvel_api.fixture.json
And I GET /users/2
Then http response code should be 200
And http response body path $.id should be 2
And http response body path $.firstName should be Bruce
And http response body path $.lastName should be WAYNE
And http response body path $.age should be 50
Scenario: Get wrong user
When I GET /users/24333
Then http response code should be 404
And http response body path $ should not have content
Scenario: Delete wrong user
When I DELETE /users/3
Then http response code should be 404
And http response body path $ should not exist
And http response body path $ should not have content
Scenario: Delete user
When I DELETE /users/1
Then http response code should be 200
And I DELETE /users/2
Then http response code should be 200
And I DELETE /users/15948349393
Then http response code should be 200
And I GET /users
And http response body is typed as array using path $ with length 0
And http response body path $ should not have content
You can look at the users.feature file for a more detailed example.
Share data between steps
- You can use the following step to store data from a json response body to a shared context:
And I store the value of body path $.id as idUser in scenario scope
- You can use the following step to store data from a response header to a shared context:
And I store the value of response header Authorization as authHeader in scenario scope
- The result of the JsonPath
$.id
will be stored in theidUser
variable. - To reuse this variable in another step, you can do:
When I DELETE /users/`$idUser`
And I set Authorization header to `$authHeader`
How to use it in my existing project ?
You can see a usage example in the test folder.
Add a CucumberTest file
@RunWith(Cucumber.class)
@CucumberOptions(
plugin = {"pretty"},
features = "src/test/resources/features",
glue = {"fr.redfroggy.bdd.restapi.glue"})
public class CucumberTest {
}
- Set the glue property to
fr.redfroggy.bdd.glue
and add your package glue. - Set your
features
folder property - Add your
.feature
files under yourfeatures
folder - In your
.feature
files you should have access to all the steps defined in the DefaultRestApiBddStepDefinition file.
Add default step definition file
It is mandatory to have a class annotated with @CucumberContextConfiguration
to be able to run the tests. This class must be in the same glue
package that you've specified in the CucumberTest
class.
@CucumberContextConfiguration
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DefaultStepDefinition {
}
Specify an authentication mode
- You can authenticate using the step:
I authenticate with login/password (.*)/(.*)
but the authentication mode must be implemented by you. - You need to implement the
BddRestTemplateAuthentication
interface. - You can inject a
TestRestTemplate
instance in your code, so you can do pretty much anything you want. - For example, for a JWT authentication you can do :
@CucumberContextConfiguration
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DefaultStepDefinition implements BddRestTemplateAuthentication {
final TestRestTemplate template;
public DefaultRestApiStepDefinitionTest(TestRestTemplate template) {
this.template = template;
}
@Override
public TestRestTemplate authenticate(String login, String password) {
String token = generateJwt();
restTemplate.getRestTemplate().getInterceptors().add(
(outReq, bytes, clientHttpReqExec) -> {
outReq.getHeaders().set(
HttpHeaders.AUTHORIZATION, token
);
return clientHttpReqExec.execute(outReq, bytes);
});
return restTemplate;
}
}
- For a basic authentication, you can do :
@CucumberContextConfiguration
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DefaultStepDefinition implements BddRestTemplateAuthentication {
final TestRestTemplate template;
public DefaultRestApiStepDefinitionTest(TestRestTemplate template) {
this.template = template;
}
@Override
public TestRestTemplate authenticate(String login, String password) {
return this.template.withBasicAuth(login, password);
}
}
- If you use a specific class, it must be annotated with
@Component
to be detected by spring context scan.
@Component
public class BasicAuthAuthentication implements BddRestTemplateAuthentication {
final TestRestTemplate template;
public BasicAuthAuthentication(TestRestTemplate template) {
this.template = template;
}
@Override
public TestRestTemplate authenticate(String login, String password) {
return this.template.withBasicAuth(login, password);
}
}
Mock third party call
If you need to mock a third party API, you can use the following steps:
I mock third party api call (.*) (.*) with return code (.*), content type: (.*) and body: (.*)
# Example: I mock third party api call GET /public/characters/1?format=json with return code 200, content type: application/json and body: {"comicName": "IronMan", "city": "New York", "mainColor": ["red", "yellow"]}
I mock third party api call (.*) (.*) with return code (.*), content type: (.*) and file: (.*)
# Example: I mock third party api call GET /public/characters/2 with return code 200, content type: application/json and file: fixtures/bruce_wayne_marvel_api.fixture.json
It relies on WireMock for stubbing api calls. By default, the wiremock port is 8888
, if you need to override it you need to change the redfroggy.cucumber.restapi.wiremock.port
property in your project.
Run local unit tests
$ mvn test