kontakt@tujestbug.pl

Jak napisać pierwszy test dla Rest Api w Javie?

Z dzisiejszego artykułu, drugiego z serii poświęconym testom REST API dowiesz się:

  1. Czym jest RestAssured
  2. Jak utworzyć nowy projekt do testowania Rest Api?
  3. W jaki sposób przygotować proste testy weryfikujące działanie Endpointów w Twojej aplikacji?

Spis treści

Czym jest RestAssured?

RestAssured jest to biblioteka pozwalająca na przesyłanie żądań oraz analizę odpowiedzi w trakcie komunikacji z Rest Api. Czyli pozwala to na testowanie poprawności Rest Api (więcej o testowaniu Rest Api znajdziesz >tutaj<).
Biblioteka przeznaczona jest do użytku wraz z językiem programowania Java, a jej autorem i głównym kontrybutorem jest Johan Haleby. Bibliotekę najłatwiej jest pobrać z strony mvnrepository.com

Jak utworzyć nowy projekt do testowania Rest Api?

Z względu na to, że mamy do czynienia z biblioteką zewnętrzną należy utworzyć projekt typu Maven/Gradle. Najprościej jest to zrobić poprzez utworzenie projektu typu Maven w naszym ide np. IntelliJ.
interfejs projektu IntelliJ
W celu dodania biblioteki RestAssured konieczne jest rozbudowanie pliku konfiguracyjnego pom.xml o odpowiednią dependencję z strony mvnrepository:
				
					<dependencies>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.3.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

				
			
Nie możemy też zapomnieć o bibliotece do wykonywania testów – tutaj zastosujemy najnowszą wersję TestNG, którą pobierzemy również przez mvnrepository:
				
					<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.7.1</version>
    <scope>test</scope>
</dependency>

				
			
Cały plik pom.xml znajdziesz >tutaj<

RestAssured tutorial czyli jak napisać pierwszy test korzystając z RestAssured?

Dla kogoś kto zetknął się z testami Selenium, czy testami jednostkowymi, nie ma tu większej zagwozdki – standardowo tworzymy nową klasę w ścieżce src/test/java, a następnie tworzymy nową metodę publiczną, gdzie umieszczamy kod testowy:

				
					@Test
public void getFirstUserDataTest(){
    given()
        .when()
            .get("https://reqres.in/api/users/1")
        .then()
            .statusCode(200);
}

				
			

Powyższy test powoduje wysłanie żądania (requestu) do https://reqres.in/api/users/1, a następnie weryfikuje, czy serwer ten zwrócił właściwy kod odpowiedzi, tj. 200 w tym przypadku.

Z czego składa się test w RestAssured?

Gdy przyjrzymy się przytoczonemu wcześniej przykładowi to dostrzeżemy pewien schemat z zwinnego podejścia BDD (Behaviour driven development) czyli typową składnie Gherkinagiven, when, then

Given...
  • pozwala na zdefiniowane strony bazowej (tzw. BaseURL) czyli np. https://reqres.in/api
  • pozwala na konfigurację przesyłania danych takich jak drukowanie logów czy filtrację danych
  • pozwala na przesłanie dodatkowych parametrów oraz headerów w requeście (doprecyzowanie requestu)
When…
  • stanowi słowo kluczowe w połączeniu wszystkich fraz

  •  jest to ten krok, po którym dochodzi do wykonania głównej akcji będącej elementem testu takiej jak np. przesłanie żądania typu get/delete/post itd.
Then...
  • weryfikująca część metody testowej

  • inicjuje wywołanie metod weryfikujących takie parametry jak:

    • kod odpowiedzi z serwera
    • nagłówki odpowiedzi serwera
    • weryfikację wybranej części ciała odpowiedzi z serwera

Weryfikacja ciała odpowiedzi z serwera

Przyjrzyjmy się kolejnemu przykładowi – tym razem z odpowiedzi serwera chcemy wyciągnąć i porównać nazwisko użytkownika z numerem id=2.

Żądanie do serwera:

				
					GET https://reqres.in/api/users/2
				
			

Odpowiedź z serwera:

				
					{
    "data": {
        "id": 2,
        "email": "janet.weaver@reqres.in",
        "first_name": "Janet",
        "last_name": "Weaver",
        "avatar": "https://reqres.in/img/faces/2-image.jpg"
    },
    "support": {
        "url": "https://reqres.in/#support-heading",
        "text": "To keep ReqRes free, contributions towards server costs are appreciated!"
    }
}

				
			

Interesujące nas dane przechowywane są w węźle data->last_name.

Najprostszy sposób, aby się do nich dostać to bezpośrednie wskazanie ścieżki w ramach metody body („data.last_name„).

Następnie pozostanie już tylko przyrównać to co otrzymamy do oczekiwanej przez nas wartości – tu z pomocą przyjdzie statyczny matcher ‘equalTo()’, który porównuje czy obie wartości są identyczne.

Finalnie nasz test przyjmie następującą formę:

				
					@Test
public void verifySecondUserLastNameTest(){
    given()
        .baseUri("https://reqres.in/api")
        .pathParam("userId", 2)
    .when()
        .get("/users/{userId}")
    .then()
        .body("data.last_name", equalTo("Weaver"));
}
				
			

Co z innymi rodzajami żądań?

Rozpatrzmy kolejny test – w tym przykładzie prześlemy dodatkowy nagłówek żądania w celu wylistowania go przez serwer w węźle ‘headers

Żądanie:

				
					POST https://postman-echo.com/post
				
			

Ciało odpowiedzi z serwera (Body):

				
					Ciało odpowiedzi serwera:
{
    "args": {
        
    },
    "data": "",
    "files": {
        
    },
    "form": {
        
    },
    "headers": {
        "x-forwarded-proto": "https",
        "x-forwarded-port": "443",
        "host": "postman-echo.com",
        "x-amzn-trace-id": "Root=1-6420a65a-750ea1065c66d36a46bf2e42",
        "content-length": "0",
        "content-type": "application/x-www-form-urlencoded",
        "accept": "*/*",
        "myheader": "tu_jest_bug",
        "user-agent": "Apache-HttpClient/4.5.13 (Java/17.0.3.1)",
        "accept-encoding": "gzip,deflate"
    },
    "json": null,
    "url": "https://postman-echo.com/post"
}

				
			

Celem testu będzie zweryfikowanie, że istnieje pole ‘myheader’ w węźle ‘headers’ o wartości ‘tu_jest_bug

				
					@Test
public void verifyHeadersAreSent(){
    given()
        .baseUri("https://postman-echo.com")
        .header("myheader", HEADER_VALUE)
    .when()
        .post("/post")
    .then()
        .statusCode(200)
        .body("headers.myheader", equalTo(HEADER_VALUE));
}

				
			

Po uruchomieniu testu … otrzymamy błąd!

				
					java.lang.AssertionError: 1 expectation failed.
Expected status code <200> but was <500>.
				
			

O ile nie posiedliśmy jakiejś tajemnej wiedzy, może być dość trudno wytypować dlaczego serwer zwraca błąd 500 – ponieważ nie otrzymaliśmy żadnej wiadomości.
Z pomocą przyjdą nam wcześniej wspomniane logiczyli zapisy komunikacji pomiędzy nami a serwerem.

Logi w RestAssured są zapisywane zarówno dla żądań jak i dla odpowiedzi – wtedy gdy zdefiniujemy dodatkową akcję log() po :

  • when() – zapisywanie logów z zapytań do serwera
  • then() – zapisywanie logów z odpowiedzi z serwera

Po dodaniu logów nasz test zawiera dodatkowe 2 linie kodu:

				
					@Test
public void verifyHeadersAreSent(){
    given()
        .baseUri("https://postman-echo.com")
        .header("myheader", HEADER_VALUE)
    .when()
        .log().all()
        .post("/post")
    .then()
        .log().all()
        .statusCode(200)
        .body("headers.myheader", equalTo(HEADER_VALUE));
}

				
			
A w wyniku jego uruchomienia otrzymujemy następujące informacje:
				
					Żądanie:
Request method:	POST
Request URI:	https://postman-echo.com/post
Proxy:			<none>
Request params:	<none>
Query params:	<none>
Form params:	<none>
Path params:	<none>
Headers:		myheader=tu_jest_bug
				Accept=*/*
				Content-Type=application/x-www-form-urlencoded; charset=ISO-8859-1
Cookies:		<none>
Multiparts:		<none>
Body:			<none>

Odpowiedź:
HTTP/1.1 500 Internal Server Error
Date: Mon, 03 Apr 2023 06:40:57 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 428
Connection: keep-alive
ETag: W/"1ac-PU9dCX2qf/xFu9z0Bpbft2FRhB4"
set-cookie: sails.sid=s%3A-t-1qTRrGTqp-W_gELypreuk0pVe8G0_.4HeWA%2BE78M%2Fcw3ZgVd%2FfyTY9cTlfEihUWMocbelTOkU; Path=/; HttpOnly

{
    "charset": "iso-8859-1",
    "type": "charset.unsupported",
    "level": "error",
    "event": "unsupported charset \"ISO-8859-1\"",
    "timestamp": 1680504057692,
    "entity.name": "echo: production",
    "entity.type": "SERVICE",
    "hostname": "echo-deployment-1-84fc8c6c74-42xbh",
    "trace.id": "2ddc02d74d578cbacf4096543631f1d4",
    "span.id": "840a950a15a5b7ca",
    "entity.guid": "MjY2NTkxOHxBUE18QVBQTElDQVRJT058MTQxNzMwNTI4Ng"
}

				
			

Po przeanalizowaniu logów widać jak na dłoni w czym jest problem!
RestAssured przesyła do serwera zapytanie kodując je w domyślnym charsecie dla języka polskiego ISO-8859-1, który nie jest obsługiwany przez sewer postman-echo.com
Aby temu zapobiec i wymusić kodowanie w UTF-8 wspieranym przez postman-echo.com będziemy musieli dodać dodatkową linię kodu dotyczącą konfigracji:

				
					@Test
public void verifyHeadersAreSent(){
    given()
        .config(config().encoderConfig(EncoderConfig.encoderConfig()
        .appendDefaultContentCharsetToContentTypeIfUndefined(false)))
        .baseUri("https://postman-echo.com")
        .header("myheader", HEADER_VALUE)
    .when()
        .log().all()
        .post("/post")
    .then()
        .log().all()
        .statusCode(200)
        .body("headers.myheader", equalTo(HEADER_VALUE));
}

				
			

Po uruchomieniu w/w kodu powinniśmy zobaczyć header ‘tu_jest_bug’ w odpowiedzi z serwera, a sam też zakończy się sukcesem. Dobra robota!

Co będziemy weryfikować korzystając z RestAssured?

Za testową część RestAssured odpowiada kod poprzedzony słowem then(). Jak pokazałem w poprzednich przykładach najczęściej weryfikować będziemy:

  • Kod odpowiedzi serwera
    • 2XX – np. 200, oznaczające, że wszystko poszło OK, może to też być taki pierwszy ‘check’ jak pokazano w przykładzie trzecim, po którym wykonujemy pozostałe testy
    • 4XX – np. 404, oznacza, że żądanie klienta jest niepoprawne. Wbrew pozorom są to istotne, a często pomijane testy, które pozwalają upewnić się, że ‘backend’ wie co robi i odpowiednio obsługuje niepoprawne żądania
  • Ciało odpowiedzi z serwera
    • Tutaj najczęściej szukamy konkretnych wartości, które chcemy porównać z oczekiwanymi danymi
    • Zdarza się, że z odpowiedzi z serwera chcemy wyciągnąć konkretną wartość, np. id jakiegoś zasobu, aby wysłać kolejne żądanie do innego endpointu aplikacji w celu dalszych testów
  • Nagłówki odpowiedzi z serwera czyli ‘Headery’
  • Czas odpowiedzi
  • Inne 🙂
Jeśli podobał Ci się artykuł, zostaw komentarz i jeśli tego jeszcze nie zrobiłeś to koniecznie dopisz się do listy subskrybentów, aby nie ominęły Cię kolejne artykuły z serii o RestAssured. Pozdrawiam! PiotrP.S. A jeśli jesteś szczególnie zainteresowny tym tematem to zapraszam Cie na najbliższe szkolenie z testowania Rest API, które poprowadzę już 1 i 2 czerwca dla testerzy.pl!

0 komentarzy

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *