Using REST test libraries with Jupiter

REST APIs are becoming more and more pervasive nowadays. For that reason, a proper strategy for assessing REST services is desirable. In this section, we are going to learn how to use several test libraries in our JUnit 5 tests.

First of all, we can use the open source library REST Assured (http://rest-assured.io/). REST Assured allows the validation of REST services by means of a fluent API inspired in dynamic languages such as Ruby or Groovy. To use REST Assured in our test project, we simply need to add the proper dependency in Maven:

<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>

or in Gradle:

dependencies {
testCompile("io.rest-assured:rest-assured:${restAssuredVersion}")
}

After that, we can use the REST Assured API. The following class contains two test examples. First sends a request to the free online REST service http://echo.jsontest.com/. Then verifies if the response code and the body content are as expected. The second test consumes another free online REST service (http://services.groupkt.com/) and also verifies the response:

package io.github.bonigarcia;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.Test;

public
class PublicRestServicesTest {

@Test
void testEchoService() {
String key = "foo";
String value = "bar";
given().when().get("http://echo.jsontest.com/" + key + "/" + value)
.then().assertThat().statusCode(200).body(key,
equalTo(value));
}

@Test
void testCountryService() {
given().when()
.get("http://services.groupkt.com/country/get/iso2code/ES")
.then().assertThat().statusCode(200)
.body("RestResponse.result.name", equalTo("Spain"));
}

}

Running this test in console with Maven, we can check that both tests succeed:

Execution of test using REST Assured

In the second example, we are going to study, in addition to the test, we are also going to implement the server side, that is, the REST service implementation. To that aim, we are going to use Spring MVC and Spring Boot, previously introduced on this chapter (see section Spring).

The implementation of REST services in Spring is quite straightforward. First, we simply need to annotate a Java class with @RestController. In the body of this class, we need to add methods annotated with @RequestMapping. These methods will listen to the different URLs (endpoints) implemented in our REST API. The accepted elements for the @RequestMapping are:

  • value: This is the path mapping URL.
  • method: This finds the HTTP request methods to map to.
  • params: This finds parameters of the mapped request, narrowing the primary mapping.
  • headers: his finds the headers of the mapped request.
  • consumes: This finds consumable media types of the mapped request.
  • produces: This finds producible media types of the mapped request.

As can be seen inspecting the code of the following class, our service example implements three different operations: GET /books (to read all book in the system), GET /book/{index} (to read a book given its identifier), and POST /book (to create a book).

package io.github.bonigarcia;

import
java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyRestController {

@Autowired
private LibraryService libraryService;

@RequestMapping(value = "/books", method = RequestMethod.GET)
public List<Book> getBooks() {
return libraryService.getBooks();
}

@RequestMapping(value = "/book/{index}", method = RequestMethod.GET)
public Book getTeam(@PathVariable("index") int index) {
return libraryService.getBook(index);
}

@RequestMapping(value = "/book", method = RequestMethod.POST)
public ResponseEntity<Boolean> addBook(@RequestBody Book book) {
libraryService.addBook(book);
return new ResponseEntity<Boolean>(true, HttpStatus.CREATED);
}

}

Since we are implementing a Jupiter test for Spring, we need to use the SpringExtension and also the SpringBootTest annotation. As a novelty, we are going to inject a test component provided by spring-test, named TestRestTemplate.

This component is a wrapper of the standard Spring's RestTemplate object, which allows to implement REST clients in a seamless way. In our test, it requests to our service (which is started before executing the tests), and responses are used to verify the outcome.

Notice that the object MockMvc (explained in the section Spring) could be also used to test REST services. The difference with respect to TestRestTemplate is that the former is used to test from the client-side (that is, response code, body, content type, and so on), while the the latter is used to test the service from the server side. For instance, in the example here, the responses to the service calls (getForEntity and postForEntity) are Java objects, whose scope is only the server side (in the client side, this information is serialized as JSON).
package io.github.bonigarcia;

import static org.junit.Assert.assertEquals;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;

import
java.time.LocalDate;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
class SpringBootRestTest {

@Autowired
TestRestTemplate restTemplate;

@Test
void testGetAllBooks() {
ResponseEntity<Book[]> responseEntity = restTemplate
.getForEntity("/books", Book[].class);
assertEquals(OK, responseEntity.getStatusCode());
assertEquals(3, responseEntity.getBody().length);
}

@Test
void testGetBook() {
ResponseEntity<Book> responseEntity = restTemplate
.getForEntity("/book/0", Book.class);
assertEquals(OK, responseEntity.getStatusCode());
assertEquals("The Hobbit", responseEntity.getBody().getName());
}

@Test
void testPostBook() {
Book book = new Book("I, Robot", "Isaac Asimov",
LocalDate.of(1950, 12, 2));
ResponseEntity<Boolean> responseEntity = restTemplate
.postForEntity("/book", book, Boolean.class);
assertEquals(CREATED, responseEntity.getStatusCode());
assertEquals(true, responseEntity.getBody());
ResponseEntity<Book[]> responseEntity2 = restTemplate
.getForEntity("/books", Book[].class);
assertEquals(responseEntity2.getBody().length, 4);
}

}

As shown in the screenshot below, our Spring application is started before running our tests, which are executed successfully:

Output of Jupiter test using TestRestTemplate to verify a REST service.

To conclude this section, we see an example in which the library WireMock (http://wiremock.org/) is used. This library allows to mock REST services, that is, a so-called HTTP mock server. This mock server captures incoming requests to the service, providing stubbed responses. This capability is very useful to test a system which consumes a REST service, but the service is not available during the tests (or we can test the component that calls the service in isolation).

As usual, we see an example to demonstrate its usage. Let's suppose we have a system which consumes a remote REST service. To implement a client for that service we use Retrofit 2 (http://square.github.io/retrofit/), which is a highly configurable HTTP client for Java. We define the interface to consume this service as illustrated in the class below. Notice that the service exposes three endpoints aimed to read a remote file (open file, read stream, and close stream):

package io.github.bonigarcia;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.POST;
import retrofit2.http.Path;

public
interface RemoteFileApi {

@POST("/api/v1/paths/{file}/open-file")
Call<ResponseBody> openFile(@Path("file") String file);

@POST("/api/v1/streams/{streamId}/read")
Call<ResponseBody> readStream(@Path("streamId") String streamId);

@POST("/api/v1/streams/{streamId}/close")
Call<ResponseBody> closeStream(@Path("streamId") String streamId);

}

Then we implement the class which consumes the REST service. In this example, it is a simple Java class which connects to the remote service given its URL passed as constructor parameter:

package io.github.bonigarcia;

import java.io.IOException;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public
class RemoteFileService {

private RemoteFileApi remoteFileApi;

public RemoteFileService(String baseUrl) {
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseUrl).build();
remoteFileApi = retrofit.create(RemoteFileApi.class);
}

public byte[] getFile(String file) throws IOException {
Call<ResponseBody> openFile = remoteFileApi.openFile(file);
Response<ResponseBody> execute = openFile.execute();
String streamId = execute.body().string();
System.out.println("Stream " + streamId + " open");

Call<ResponseBody> readStream = remoteFileApi.readStream(streamId);
byte[] content = readStream.execute().body().bytes();
System.out.println("Received " + content.length + " bytes");

remoteFileApi.closeStream(streamId).execute();
System.out.println("Stream " + streamId + " closed");

return content;
}

}

Finally, we implement a JUnit 5 test to verify our service. Notce that we are creating the mock server (new WireMockServer) and stubbing the REST service calls using the static methods stubFor(...) provided by WireMock in the setup of the test (@BeforeEach). Since in this case, the SUT is very simple and it has no DOCs, we directly instantiate the class RemoteFileService also in the setup of each test, using the mock server URL as constructor argument. Finally, we test our service (which uses the mock server) simply exercising the object called wireMockServer, in this example, by calling to the method getFile and assessing its output.


package io.github.bonigarcia;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.jupiter.api.Assertions.assertEquals;

import
java.io.IOException;
import java.net.ServerSocket;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.github.tomakehurst.wiremock.WireMockServer;

public class RemoteFileTest {

RemoteFileService remoteFileService;
WireMockServer wireMockServer;

// Test data
String filename = "foo";
String streamId = "1";
String contentFile = "dummy";

@BeforeEach
void setup() throws Exception {
// Look for free port for SUT instantiation
int port;
try (ServerSocket socket = new ServerSocket(0)) {
port = socket.getLocalPort();
}
remoteFileService = new RemoteFileService("http://localhost:" +
port);

// Mock server
wireMockServer = new WireMockServer(options().port(port));
wireMockServer.start();
configureFor("localhost", wireMockServer.port());

// Stubbing service
stubFor(post(urlEqualTo("/api/v1/paths/" + filename + "/open-
file"))
.willReturn(aResponse().withStatus(200).withBody(streamId)));
stubFor(post(urlEqualTo("/api/v1/streams/" + streamId +
"/read"))
.willReturn(aResponse().withStatus(200).withBody(contentFile)));
stubFor(post(urlEqualTo("/api/v1/streams/" + streamId + /close"))
.willReturn(aResponse().withStatus(200)));
}

@Test
void testGetFile() throws IOException {
byte[] fileContent = remoteFileService.getFile(filename);
assertEquals(contentFile.length(), fileContent.length);
}

@AfterEach
void teardown() {
wireMockServer.stop();
}

}

Executing the test in the console, in the traces we can see how the internal HTTP server controlled by WireMock is started before the test execution. Then, the three REST operations (open stream, read bytes, close stream) are executed by the test, and finally the mock server is disposed:

Execution of test using a mock REST server using WireMock
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.194