RestAssured and Hamcrest
I dag skal vi i gang med at teste vores API-endpoints. Når vi bygger en backend i Javalin, er det ikke nok bare at starte serveren og klikke lidt rundt. Vi vil gerne kunne kontrollere automatisk, at vores endpoints virker som forventet — også når vi senere ændrer i koden.
Til det bruger vi to biblioteker:
Rest Assured
bruges til at sende HTTP-kald i tests, fx GET, POST, PUT og DELETE.
Hamcrest matchers bruges til at skrive forventninger på en læsbar måde, fx:
- at statuskoden er
200 - at et felt i JSON er
"Fido" - at en liste har størrelse
3
Fordelen er, at vi kan teste vores API næsten som en rigtig klient ville bruge den.
Vi kan fx teste:
- returnerer endpointet den rigtige statuskode?
- kommer data tilbage i korrekt JSON-format?
- indeholder svaret de rigtige værdier?
- fejler endpointet korrekt, hvis input er ugyldigt?
Det gør vores kode mere robust og lettere at videreudvikle.
En endpoint-test har ofte denne form:
- Send en HTTP-request
- Modtag et svar
- Tjek at svaret matcher det forventede
Eksempel i ord:
“Når jeg kalder
GET /dogs/1, forventer jeg status 200 og en hund med navnetFido.”
Forestil jer et endpoint:
app.get("/dogs/{id}", ctx -> {
DogDTO dog = new DogDTO(1, "Fido", "DACHSHUND");
ctx.json(dog);
});
Så kan vi skrive en test sådan her:
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
class DogRoutesTest {
@Test
void shouldReturnDogById() {
given()
.when()
.get("/dogs/1")
.then()
.statusCode(200)
.body("id", equalTo(1))
.body("name", equalTo("Fido"))
.body("breed", equalTo("DACHSHUND"));
}
}
Her kan vi sætte requesten op. Fx headers, content type eller body.
Her udfører vi selve HTTP-kaldet.
Her laver vi vores assertions — altså det, vi forventer.
Det gør testen ret læsbar:
- given noget
- when jeg kalder endpointet
- then forventer jeg dette resultat
Hamcrest gør assertions mere læsbare.
Nogle nyttige matchers:
equalTo(...)
notNullValue()
hasSize(...)
hasItem(...)
containsString(...)
Eksempler:
.body("name", equalTo("Fido"))
.body("message", containsString("created"))
.body("dogs", hasSize(3))
.body("dogs.name", hasItem("Fido"))
Det er især smart, når man tester JSON-svar.
Hvis vi har et endpoint der opretter en hund:
app.post("/dogs", ctx -> {
DogDTO incoming = ctx.bodyAsClass(DogDTO.class);
ctx.status(201).json(incoming);
});
Så kan testen se sådan ud:
import static io.restassured.http.ContentType.JSON;
@Test
void shouldCreateDog() {
String json = """
{
"id": 2,
"name": "Bella",
"breed": "BEAGLE"
}
""";
given()
.contentType(JSON)
.body(json)
.when()
.post("/dogs")
.then()
.statusCode(201)
.body("name", equalTo("Bella"))
.body("breed", equalTo("BEAGLE"));
}
Når I tester endpoints, så tænk i mindst disse tre typer:
Når alt er korrekt.
Eksempel:
GET /dogs/1giver200- returnerer korrekt data
Når noget går galt.
Eksempel:
GET /dogs/999giver404POST /dogsuden navn giver400
Når input er tomt, mærkeligt eller mangelfuldt.
Eksempel:
- tom request body
- ugyldig enum-værdi
- id der ikke findes
@Test
void shouldReturn404WhenDogNotFound() {
given()
.when()
.get("/dogs/999")
.then()
.statusCode(404);
}
Det er vigtigt at teste fejl også — ikke kun når systemet virker.
Ofte gør man noget i stil med dette:
- starter en testserver før hver test eller før alle tests
- peger Rest Assured på serverens base URI og port
- rydder testdata op mellem tests
Fx:
import org.junit.jupiter.api.BeforeAll;
import static io.restassured.RestAssured.baseURI;
import static io.restassured.RestAssured.port;
@BeforeAll
static void setup() {
baseURI = "http://localhost";
port = 7070;
}
Hvis I laver integrationstests, er det en god idé at bruge en separat testdatabase.
Rest Assured tester ikke jeres metoder direkte. Det tester jeres HTTP-lag.
Det betyder, at testen er tættere på virkeligheden end en almindelig unit test.
I tester fx:
- routing
- request parsing
- JSON serialisering/deserialisering
- statuskoder
- response body
Det er derfor super relevant til Javalin.
Rest Assured bruges til at sende HTTP-requests i tests. Hamcrest bruges til at formulere forventninger til svaret. Sammen gør de det muligt at teste vores Javalin endpoints på en læsbar og realistisk måde.
Målet er ikke bare at se om koden “kører”, men om API’et opfører sig korrekt.
given()
.when()
.get("/dogs/1")
.then()
.statusCode(200)
.body("name", equalTo("Fido"));
Det kan forklares som:
“Når jeg kalder endpointet
/dogs/1, så forventer jeg status 200 og at navnet er Fido.”
Lad de studerende skrive tests til disse endpoints:
GET /dogs→ returnerer liste af hundeGET /dogs/{id}→ returnerer én hundPOST /dogs→ opretter en hundGET /dogs/999→ returnerer 404
De skal mindst teste:
- statuskode
- et eller flere JSON-felter
- et fejlscenarie