Quand on revient de conférences, il arrive souvent qu’on ait une liste de choses à voir longue comme le bras. Et de mon côté, après avoir vu la conférence “Architecture Hexagonale Level 2 : Comment bien écrire ses tests.” au BreizhCamp 2019 de Julien Topçu et Jordan Nourry et bien j’avais noté dans un coin de ma tête Karate.
Karate c’est quoi ?
Karate est un framework Open-source qui affiche comme promesse : “API test-automation, mocks and performance-testing into a single, unified framework”. Dans un premier temps, on va s’intéresser uniquement à ce qu’il offre pour les tests d’API automatisés.
Étant un développeur respectueux de la pyramide de tests, je teste mes API en mode end-2-end. Et là où Karate diffère des classiques du genre (http://rest-assured.io/) c’est dans la façon d’écrire ces tests.
En effet, quand on utilise ce framework, on ne code pas (ou peu) mais on écrit des fichiers en simili-Gherkin (https://cucumber.io/docs/gherkin/) ce qui nous donne des tests human-readable (pour peu que l’on soit à l’aise avec la langue de shakespeare).
Mise en place et tests de notre API
Maven
Pour utiliser Karate dans notre projet il faut ajouter deux librairies :
<dependency>
<groupId>com.intuit.karate</groupId>
<artifactId>karate-apache</artifactId>
<version>0.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.intuit.karate</groupId>
<artifactId>karate-junit4</artifactId>
<version>0.9.3</version>
<scope>test</scope>
</dependency>
A noter que Karate offre peut aussi être utilisé avec l’implémentation du client HTTP Jersey (karate-jersey) et avec junit5 (karate-junit5).
Configuration
Afin de ne pas se répéter et de disposer de flexibilité quant à l’URL de l’API que nous allons utiliser, Karate permet l’ajout d’un fichier de configuration à ajouter dans le classpath de tests se nommant karate-config.js.
Voici son contenu :
function fn() {
var env = karate.env; // get java system property "karate.env"
karate.log("karate.env system property was:", env);
if (!env) {
env = "dev"; // a custom "intelligent" default
}
var config = { // base config JSON
baseUrl: "http://localhost:8445/api"
};
if (env === "stage") {
// override only those that need to be
config.baseUrl = "https://stage-host/api";
} else if (env === "e2e") {
config.baseUrl = "https://e2e-host/api";
}
// don"t waste time waiting for a connection or if servers don"t respond within 5 seconds
karate.configure("connectTimeout", 5000);
karate.configure("readTimeout", 5000);
return config;
}
Elle nous permet de définir selon les profils des adresses différentes pour notre API ainsi que la gestion des timeouts.
Pour plus d’infos c’est par ici : https://github.com/intuit/karate#configuration
Tests
Maintenant, place à l’écriture des tests e2e. Pour ce faire, un classique Runner Junit est proposé par Karate, il suffit donc de créer une classe (en Kotlin ici) comme suit :
import com.intuit.karate.junit4.Karate
import org.junit.runner.RunWith
@RunWith(Karate::class)
class OctopusApiIT
Imaginons que notre API REST Octopus !🐙 propose ces opérations :
- GET http://localhost:8080/api/octopus
- GET http://localhost:8080/api/octopus/{id}
- POST http://localhost:8080/api/octopus
- DELETE http://localhost:8080/api/octopus/{id}
Ecrivons donc un simple test Karate nous permettant de parcourir toutes ces API.
Par défaut, lors du lancement de notre test OctopusApiIT, Karate ira chercher tous les fichiers .feature au même niveau. Ok, c’est parti !
Voilà le contenu de notre fichier octopus-api.feature
Feature: Octopus management API
Background:
* url baseUrl
Scenario: Octopus creation and deletion
# Octopus creation
Given path 'octopus'
And request { color : "blue", traits : ["cool"]}
When method post
Then status 201
And match response == { id: '#uuid', color : "blue", traits : ["cool"] }
And def createdOctopusId = response.id
# Octopus retrieval
Given path 'octopus', createdOctopusId
When method get
Then status 200
And match response == { id: '#(createdOctopusId)' }
# Octopus retrieval on global path
Given path 'octopus'
When method get
Then status 200
And match response[*].id contains == createdAuthRequestId
# Octopus deletion
Given path 'octopus', createdOctopusId
When method delete
Then status 204
# Check Octopus has been deleted
Given path 'octopus', createdOctopusId
When method get
Then status 404
En moins de 40 lignes de textes, nous allons donc effectuer 5 requêtes sur notre API avec des contrôles :
- sur les codes HTTP (exemple :
Then status 204
) - sur le contenu du body JSON de retour
And match response == { id: '#uuid', color : "blue", traits : ["cool"] }
- sur ce point Karate dispose d’une syntaxe d’assertions en mode pattern matching plutôt simple et efficace. Voir https://github.com/intuit/karate#match
Désormais nous n’avons plus qu’à lancer le test OctopusApiIT et profiter !
Aller plus loin dans nos Katas
Bon jusque là, ça à l’air sympa et rapide mais vous allez me dire “Comment je fais pour gérer mon IAM avec tout ça ?”.
Et bien heureusement Karate nous permet de faire du reuse de feature. Ainsi, on peut ajouter avant chaque test (section Background), un appel à notre feature gérant l’authentification.
Feature: which makes a 'call' to another re-usable feature
Background:
* configure headers = read('classpath:my-headers.js')
* def signIn = call read('classpath:my-signin.feature') { username: 'john', password: 'secret' }
* def authToken = signIn.authToken
Scenario: some scenario
# main test steps
Et la feature se chargeant de l’authentification
Feature: here are the contents of 'my-signin.feature'
Scenario:
Given url loginUrlBase
And request { userId: '#(username)', userPass: '#(password)' }
When method post
Then status 200
And def authToken = response
Parfait pour l’intégration dans nos projets Keycloak !🙂
Pour conclure
Si ce rapide billet sur Karate ne vous a pas convaincu, nous oui !
La grandissante popularité du framework est plutôt méritée, l’outil semble déjà plutôt mature et fournit déjà un éventail de fonctionnalités conséquent. Il apparait d’ailleurs désormais sur le fameux radar Toughtworks en Assess https://www.thoughtworks.com/radar/languages-and-frameworks.
Bref, cela vaut le coup d’oeil !