Pengujian REST API dengan Mentimun

1. Ikhtisar

Tutorial ini memberikan pengantar tentang Mentimun, alat yang umum digunakan untuk pengujian penerimaan pengguna, dan cara menggunakannya dalam pengujian REST API.

Selain itu, untuk membuat artikel mandiri dan independen dari layanan REST eksternal apa pun, kami akan menggunakan WireMock, pustaka layanan web stubbing dan mocking. Jika Anda ingin tahu lebih banyak tentang perpustakaan ini, silakan lihat pengantar WireMock.

2. Gherkin - Bahasa Mentimun

Cucumber adalah framework pengujian yang mendukung Behavior Driven Development (BDD), memungkinkan pengguna untuk menentukan operasi aplikasi dalam teks biasa. Ia bekerja berdasarkan Gherkin Domain Specific Language (DSL). Sintaks Gherkin yang sederhana namun kuat ini memungkinkan pengembang dan penguji menulis pengujian yang rumit sekaligus menjaganya tetap dapat dipahami bahkan oleh pengguna non-teknis.

2.1. Pengantar Gherkin

Gherkin adalah bahasa berorientasi baris yang menggunakan akhiran baris, lekukan, dan kata kunci untuk menentukan dokumen. Setiap baris yang tidak kosong biasanya dimulai dengan kata kunci Gherkin, diikuti dengan teks sembarang, yang biasanya merupakan deskripsi dari kata kunci tersebut.

Seluruh struktur harus ditulis ke dalam file dengan ekstensi fitur untuk dikenali oleh Mentimun.

Berikut adalah contoh dokumen Gherkin sederhana:

Feature: A short description of the desired functionality Scenario: A business situation Given a precondition And another precondition When an event happens And another event happens too Then a testable outcome is achieved And something else is also completed

Di bagian berikut, kami akan menjelaskan beberapa elemen terpenting dalam struktur Gherkin.

2.2. Fitur

Kami menggunakan file Gherkin untuk mendeskripsikan fitur aplikasi yang perlu diuji. File tersebut berisi kata kunci Fitur di awal, diikuti oleh nama fitur di baris yang sama dan deskripsi opsional yang dapat menjangkau beberapa baris di bawahnya.

Pengurai mentimun melewatkan semua teks, kecuali untuk kata kunci Fitur , dan menyertakannya untuk tujuan dokumentasi saja.

2.3. Skenario dan Langkah

Struktur Gherkin dapat terdiri dari satu atau beberapa skenario, yang dikenali oleh kata kunci Skenario . Skenario pada dasarnya adalah tes yang memungkinkan pengguna untuk memvalidasi kemampuan aplikasi. Ini harus menggambarkan konteks awal, peristiwa yang mungkin terjadi dan hasil yang diharapkan yang dibuat oleh peristiwa tersebut.

Hal-hal ini dilakukan dengan menggunakan langkah-langkah, yang diidentifikasi oleh salah satu dari lima kata kunci: Diberikan , Kapan , Lalu , Dan , dan Tapi .

  • Diberikan : Langkah ini adalah menempatkan sistem ke dalam keadaan yang terdefinisi dengan baik sebelum pengguna mulai berinteraksi dengan aplikasi. Sebuah Mengingat klausul oleh dapat dianggap sebagai prasyarat untuk kasus penggunaan.
  • When : A When step digunakan untuk mendeskripsikan peristiwa yang terjadi pada aplikasi. Ini bisa berupa tindakan yang diambil oleh pengguna, atau peristiwa yang dipicu oleh sistem lain.
  • Kemudian : Langkah ini untuk menentukan hasil tes yang diharapkan. Hasilnya harus terkait dengan nilai bisnis dari fitur yang diuji.
  • Dan dan Tapi : Kata kunci ini dapat digunakan untuk menggantikan kata kunci langkah di atas bila ada beberapa langkah dengan jenis yang sama.

Mentimun sebenarnya tidak membedakan kata kunci ini, namun kata kunci tersebut tetap ada untuk membuat fitur lebih mudah dibaca dan konsisten dengan struktur BDD.

3. Penerapan Timun-JVM

Mentimun pada awalnya ditulis di Ruby dan telah diporting ke Java dengan implementasi Cucumber-JVM, yang merupakan subjek dari bagian ini.

3.1. Dependensi Maven

Untuk menggunakan Cucumber-JVM dalam proyek Maven, ketergantungan berikut ini perlu disertakan dalam POM:

 io.cucumber cucumber-java 6.8.0 test 

Untuk memfasilitasi pengujian JUnit dengan Mentimun, kita perlu memiliki satu ketergantungan lagi:

 io.cucumber cucumber-junit 6.8.0 

Alternatifnya, kita bisa menggunakan artefak lain untuk memanfaatkan ekspresi lambda di Java 8, yang tidak akan dibahas dalam tutorial ini.

3.2. Definisi Langkah

Skenario Gherkin tidak akan berguna jika tidak diterjemahkan ke dalam tindakan dan di sinilah definisi langkah mulai berlaku. Pada dasarnya, definisi langkah adalah metode Java beranotasi dengan pola terlampir yang tugasnya adalah mengubah langkah Gherkin dalam teks biasa menjadi kode yang dapat dieksekusi. Setelah mengurai dokumen fitur, Mentimun akan mencari definisi langkah yang cocok dengan langkah Gherkin yang telah ditentukan untuk dijalankan.

Agar lebih jelasnya, yuk kita simak langkah-langkah berikut ini:

Given I have registered a course in Baeldung

And a step definition:

@Given("I have registered a course in Baeldung") public void verifyAccount() { // method implementation }

When Cucumber reads the given step, it will be looking for step definitions whose annotating patterns match the Gherkin text.

4. Creating and Running Tests

4.1. Writing a Feature File

Let's start with declaring scenarios and steps in a file with the name ending in the .feature extension:

Feature: Testing a REST API Users should be able to submit GET and POST requests to a web service, represented by WireMock Scenario: Data Upload to a web service When users upload data on a project Then the server should handle it and return a success status Scenario: Data retrieval from a web service When users want to get information on the 'Cucumber' project Then the requested data is returned

We now save this file in a directory named Feature, on the condition that the directory will be loaded into the classpath at runtime, e.g. src/main/resources.

4.2. Configuring JUnit to Work With Cucumber

In order for JUnit to be aware of Cucumber and read feature files when running, the Cucumber class must be declared as the Runner. We also need to tell JUnit the place to search for feature files and step definitions.

@RunWith(Cucumber.class) @CucumberOptions(features = "classpath:Feature") public class CucumberIntegrationTest { }

As you can see, the features element of CucumberOption locates the feature file created before. Another important element, called glue, provides paths to step definitions. However, if the test case and step definitions are in the same package as in this tutorial, that element may be dropped.

4.3. Writing Step Definitions

When Cucumber parses steps, it will search for methods annotated with Gherkin keywords to locate the matching step definitions.

A step definition’s expression can either be a Regular Expression or a Cucumber Expression. In this tutorial, we'll use Cucumber Expressions.

The following is a method that fully matches a Gherkin step. The method will be used to post data to a REST web service:

@When("users upload data on a project") public void usersUploadDataOnAProject() throws IOException { }

And here is a method matching a Gherkin step and takes an argument from the text, which will be used to get information from a REST web service:

@When("users want to get information on the {string} project") public void usersGetInformationOnAProject(String projectName) throws IOException { }

As you can see, the usersGetInformationOnAProject method takes a String argument, which is the project name. This argument is declared by {string} in the annotation and over here it corresponds to Cucumber in the step text.

Alternatively, we could use a regular expression:

@When("^users want to get information on the '(.+)' project$") public void usersGetInformationOnAProject(String projectName) throws IOException { }

Note, the ‘^' and ‘$' which indicate the start and end of the regex accordingly. Whereas ‘(.+)' corresponds to the String parameter.

We'll provide the working code for both of the above methods in the next section.

4.4. Creating and Running Tests

First, we will begin with a JSON structure to illustrate the data uploaded to the server by a POST request, and downloaded to the client using a GET. This structure is saved in the jsonString field, and shown below:

{ "testing-framework": "cucumber", "supported-language": [ "Ruby", "Java", "Javascript", "PHP", "Python", "C++" ], "website": "cucumber.io" }

To demonstrate a REST API, we use a WireMock server:

WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());

In addition, we'll use Apache HttpClient API to represent the client used to connect to the server:

CloseableHttpClient httpClient = HttpClients.createDefault();

Now, let's move on to writing testing code within step definitions. We will do this for the usersUploadDataOnAProject method first.

The server should be running before the client connects to it:

wireMockServer.start();

Using the WireMock API to stub the REST service:

configureFor("localhost", wireMockServer.port()); stubFor(post(urlEqualTo("/create")) .withHeader("content-type", equalTo("application/json")) .withRequestBody(containing("testing-framework")) .willReturn(aResponse().withStatus(200)));

Now, send a POST request with the content taken from the jsonString field declared above to the server:

HttpPost request = new HttpPost("//localhost:" + wireMockServer.port() + "/create"); StringEntity entity = new StringEntity(jsonString); request.addHeader("content-type", "application/json"); request.setEntity(entity); HttpResponse response = httpClient.execute(request);

The following code asserts that the POST request has been successfully received and handled:

assertEquals(200, response.getStatusLine().getStatusCode()); verify(postRequestedFor(urlEqualTo("/create")) .withHeader("content-type", equalTo("application/json")));

The server should stop after being used:

wireMockServer.stop();

The second method we will implement herein is usersGetInformationOnAProject(String projectName ). Similar to the first test, we need to start the server and then stub the REST service:

wireMockServer.start(); configureFor("localhost", wireMockServer.port()); stubFor(get(urlEqualTo("/projects/cucumber")) .withHeader("accept", equalTo("application/json")) .willReturn(aResponse().withBody(jsonString)));

Submitting a GET request and receiving a response:

HttpGet request = new HttpGet("//localhost:" + wireMockServer.port() + "/projects/" + projectName.toLowerCase()); request.addHeader("accept", "application/json"); HttpResponse httpResponse = httpClient.execute(request);

We will convert the httpResponse variable to a String using a helper method:

String responseString = convertResponseToString(httpResponse);

Here is the implementation of that conversion helper method:

private String convertResponseToString(HttpResponse response) throws IOException { InputStream responseStream = response.getEntity().getContent(); Scanner scanner = new Scanner(responseStream, "UTF-8"); String responseString = scanner.useDelimiter("\\Z").next(); scanner.close(); return responseString; }

The following verifies the whole process:

assertThat(responseString, containsString("\"testing-framework\": \"cucumber\"")); assertThat(responseString, containsString("\"website\": \"cucumber.io\"")); verify(getRequestedFor(urlEqualTo("/projects/cucumber")) .withHeader("accept", equalTo("application/json")));

Finally, stop the server as described before.

5. Running Features in Parallel

Cucumber-JVM natively supports parallel test execution across multiple threads. We'll use JUnit together with Maven Failsafe plugin to execute the runners. Alternatively, we could use Maven Surefire.

JUnit runs the feature files in parallel rather than scenarios, which means all the scenarios in a feature file will be executed by the same thread.

Let's now add the plugin configuration:

 maven-failsafe-plugin ${maven-failsafe-plugin.version}   CucumberIntegrationTest.java  methods 2     integration-test verify    

Note that:

  • parallel: bisa berupa kelas, metode , atau keduanya - dalam kasus kami, kelas akan membuat setiap kelas pengujian berjalan di thread terpisah
  • threadCount: menunjukkan berapa banyak utas yang harus dialokasikan untuk eksekusi ini

Hanya itu yang perlu kita lakukan untuk menjalankan fitur Ketimun secara paralel.

6. Kesimpulan

Dalam tutorial ini, kami membahas dasar-dasar Ketimun dan bagaimana framework ini menggunakan bahasa khusus domain Gherkin untuk menguji REST API.

Seperti biasa, semua contoh kode yang ditampilkan dalam tutorial ini tersedia di GitHub.