1. Overview
In this article, we’ll introduce Karate, a Behavior Driven Development (BDD) testing framework for Java.
2. Karate and BDD
Karate is built on top of Cucumber, another BDD testing framework, and shares some of the same concepts. One of these is the use of a Gherkin file, which describes the tested feature. However, unlike Cucumber, tests aren’t written in Java and are fully described in the Gherkin file.
A Gherkin file is saved with the “.feature” extension. It begins with the Feature keyword, followed by the feature name on the same line. It also contains different test scenarios, each beginning with the keyword Scenario and consisting of multiple steps with the keywords Given, When, Then, And, and But.
More about Cucumber and the Gherkin structure can be found here.
3. Maven Dependencies
To make use of Karate in a Maven project, we need to add the karate-apache dependency to the pom.xml:
<dependency> <groupId>com.intuit.karate</groupId> <artifactId>karate-apache</artifactId> <version>0.6.0</version> </dependency>
We’ll also need the karate-junit4 dependency to facilitate JUnit testing:
<dependency> <groupId>com.intuit.karate</groupId> <artifactId>karate-junit4</artifactId> <version>0.6.0</version> </dependency>
4. Creating Tests
We’ll start by writing tests for some common scenarios in a Gherkin Feature file.
4.1. Testing the Status Code
Let’s write a scenario that tests a GET endpoint and checks if it returns a 200 (OK) HTTP status code:
Scenario: Testing valid GET endpoint Given url 'http://localhost:8080/user/get' When method GET Then status 200
This works obviously with all possible HTTP status codes.
4.2. Testing the Response
Let’s a write another scenario that tests that the REST endpoint returns a specific response:
Scenario: Testing the exact response of a GET endpoint Given url 'http://localhost:8080/user/get' When method GET Then status 200 And match $ == {id:"1234",name:"John Smith"}
The match operation is used for the validation where ‘$’ represents the response. So the above scenario checks that the response exactly matches ‘{id:”1234″,name:”John Smith”}’.
We can also check specifically for the value of the id field:
And match $.id == "1234"
The match operation can also be used to check if the response contains certain fields. This is helpful when only certain fields need to be checked or when not all response fields are known:
Scenario: Testing that GET response contains specific field Given url 'http://localhost:8080/user/get' When method GET Then status 200 And match $ contains {id:"1234"}
4.3. Validating Response Values with Markers
In the case where we don’t know the exact value that is returned, we can still validate the value using markers — placeholders for matching fields in the response.
For example, we can use a marker to indicate whether we expect a null value or not:
- #null
- #notnull
Or we can use a marker to match a certain type of value in a field:
- #boolean
- #number
- #string
Other markers are available for when we expect a field to contain a JSON object or array:
- #array
- #object
And there’re markers for matching on a certain format or regular expression and one that evaluates a boolean expression:
- #uuid — value conforms to the UUID format
- #regex STR — value matches the regular expression STR
- #? EXPR — asserts that the JavaScript expression EXPR evaluates to true
Finally, if we don’t want any kind of check on a field, we can use the #ignore marker.
Let’s rewrite the above scenario to check that the id field is not null:
Scenario: Test GET request exact response Given url 'http://localhost:8080/user/get' When method GET Then status 200 And match $ == {id:"#notnull",name:"John Smith"}
4.4. Testing a POST Endpoint with a Request Body
Let’s look at a final scenario that tests a POST endpoint and takes a request body:
Scenario: Testing a POST endpoint with request body Given url 'http://localhost:8080/user/create' And request { id: '1234' , name: 'John Smith'} When method POST Then status 200 And match $ contains {id:"#notnull"}
5. Running Tests
Now that the test scenarios are complete, we can run our tests by integrating Karate with JUnit.
We’ll use the @CucumberOptions annotation to specify the exact location of the Feature files:
@RunWith(Karate.class) @CucumberOptions(features = "classpath:karate") public class KarateUnitTest { //... }
To demonstrate the REST API, we’ll use a WireMock server.
For this example, we mock all the endpoints that are being tested in the method annotated with @BeforeClass. We’ll shut down the WireMock server in the method annotated with @AfterClass:
private static WireMockServer wireMockServer = new WireMockServer(); @BeforeClass public static void setUp() throws Exception { wireMockServer.start(); configureFor("localhost", 8080); stubFor( get(urlEqualTo("/user/get")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody("{ \"id\": \"1234\", name: \"John Smith\" }"))); stubFor( post(urlEqualTo("/user/create")) .withHeader("content-type", equalTo("application/json")) .withRequestBody(containing("id")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody("{ \"id\": \"1234\", name: \"John Smith\" }"))); } @AfterClass public static void tearDown() throws Exception { wireMockServer.stop(); }
When we run the KarateUnitTest class, the REST Endpoints are created by the WireMock Server, and all the scenarios in the specified feature file are run.
6. Conclusion
In this tutorial, we looked at how to test REST APIs using the Karate Testing Framework.
Complete source code and all code snippets for this article can be found over on GitHub.