Running GraphQL queries and mutations using a GraphQL client

duration 20 minutes
New

Prerequisites:

Learn how to use a GraphQL client to run GraphQL queries and mutations.

What you’ll learn

GraphQL is an open source data query language. You can use a GraphQL service to obtain data from multiple sources, such as APIs, databases, and other services, by sending a single request to a GraphQL service. GraphQL services require less data fetching than REST services, which results in faster application load times and lower data transfer costs. This guide assumes you have a basic understanding of GraphQL concepts. If you’re new to GraphQL, you might want to start with the Optimizing REST queries for microservices with GraphQL guide first.

You’ll use the SmallRye GraphQL client to create a query microservice that will make requests to the graphql microservice. The graphql microservice retrieves data from multiple system microservices and is identical to the one created as part of the Optimizing REST queries for microservices with GraphQL guide.

GraphQL client application architecture where multiple system microservices are integrated behind the graphql service

The results of the requests will be displayed at REST endpoints. OpenAPI will be used to help make the requests and display the data. To learn more about OpenAPI, check out the Documenting RESTful APIs guide.

Additional prerequisites

Before you begin, Docker needs to be installed. For installation instructions, refer to the official Docker documentation. You will build and run the application in Docker containers.

Make sure to start your Docker daemon before you proceed.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-graphql-client.git
cd guide-graphql-client

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Implementing a GraphQL client

Navigate to the start directory to begin.

The SmallRye GraphQL client is used to implement the GraphQL client service. The SmallRye GraphQL client supports two types of clients: typesafe and dynamic. A typesafe client is easy to use and provides a high-level approach, while a dynamic client provides a more customizable and low-level approach to handle operations and responses. You will implement a typesafe client microservice.

The typesafe client interface contains a method for each resolver available in the graphql microservice. The JSON objects returned by the graphql microservice are converted to Java objects.

Create the GraphQlClient interface.
query/src/main/java/io/openliberty/guides/query/client/GraphQlClient.java

GraphQlClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.query.client;
14
15import org.eclipse.microprofile.graphql.Query;
16import org.eclipse.microprofile.graphql.Mutation;
17import org.eclipse.microprofile.graphql.Name;
18
19import io.openliberty.guides.graphql.models.SystemInfo;
20import io.openliberty.guides.graphql.models.SystemLoad;
21import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
22
23// tag::clientApi[]
24@GraphQLClientApi
25// end::clientApi[]
26public interface GraphQlClient {
27    // tag::querySystemTag[]
28    @Query
29    // end::querySystemTag[]
30    // tag::systemInfo[]
31    SystemInfo system(@Name("hostname") String hostname);
32    // end::systemInfo[]
33
34    // tag::querySystemLoadTag[]
35    @Query("systemLoad")
36    // end::querySystemLoadTag[]
37    // tag::systemLoad[]
38    SystemLoad[] getSystemLoad(@Name("hostnames") String[] hostnames);
39    // end::systemLoad[]
40
41    // tag::mutationTag[]
42    @Mutation
43    // end::mutationTag[]
44    // tag::editNote[]
45    boolean editNote(@Name("hostname") String host, @Name("note") String note);
46    // end::editNote[]
47
48}

The GraphQlClient interface is annotated with the @GraphQlClientApi annotation. This annotation denotes that this interface is used to create a typesafe GraphQL client.

Inside the interface, a method header is written for each resolver available in the graphql microservice. The names of the methods match the names of the resolvers in the GraphQL schema. Resolvers that require input variables have the input variables passed in using the @Name annotation on the method inputs. The return types of the methods should match those of the GraphQL resolvers.

For example, the system() method maps to the system resolver. The resolver returns a SystemInfo object, which is described by the SystemInfo class. Thus, the system() method returns the type SystemInfo.

The name of each resolver is the method name, but it can be overridden with the @Query or @Mutation annotations. For example, the name of the method getSystemLoad is overridden as systemLoad. The GraphQL request that goes over the wire will use the name overridden by the @Query and @Mutation annotation. Similarly, the name of the method inputs can be overridden by the @Name annotation. For example, input host is overridden as hostname in the editNote() method.

The editNote mutation operation has the @Mutation annotation on it. A mutation operation allows you to modify data, in this case, it allows you to add and edit a note to the system service. If the @Mutation annotation were not placed on the method, it would be treated as if it mapped to a query operation.

Create the QueryResource class.
query/src/main/java/io/openliberty/guides/query/QueryResource.java

QueryResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.query;
14
15import io.openliberty.guides.graphql.models.NoteInfo;
16import io.openliberty.guides.graphql.models.SystemInfo;
17import io.openliberty.guides.graphql.models.SystemLoad;
18import io.openliberty.guides.query.client.GraphQlClient;
19import io.smallrye.graphql.client.typesafe.api.TypesafeGraphQLClientBuilder;
20import jakarta.enterprise.context.ApplicationScoped;
21import jakarta.ws.rs.Consumes;
22import jakarta.ws.rs.GET;
23import jakarta.ws.rs.POST;
24import jakarta.ws.rs.Path;
25import jakarta.ws.rs.PathParam;
26import jakarta.ws.rs.Produces;
27import jakarta.ws.rs.core.MediaType;
28import jakarta.ws.rs.core.Response;
29
30@ApplicationScoped
31@Path("query")
32public class QueryResource {
33
34    // tag::clientBuilder[]
35    private GraphQlClient gc = TypesafeGraphQLClientBuilder.newBuilder()
36                                                   .build(GraphQlClient.class);
37    // end::clientBuilder[]
38
39    @GET
40    @Path("system/{hostname}")
41    @Produces(MediaType.APPLICATION_JSON)
42    public SystemInfo querySystem(@PathParam("hostname") String hostname) {
43        // tag::clientUsed1[]
44        return gc.system(hostname);
45        // end::clientUsed1[]
46    }
47
48    @GET
49    @Path("systemLoad/{hostnames}")
50    @Produces(MediaType.APPLICATION_JSON)
51    public SystemLoad[] querySystemLoad(@PathParam("hostnames") String hostnames) {
52        String[] hostnameArray = hostnames.split(",");
53        // tag::clientUsed2[]
54        return gc.getSystemLoad(hostnameArray);
55        // end::clientUsed2[]
56    }
57
58    @POST
59    @Path("mutation/system/note")
60    @Consumes(MediaType.APPLICATION_JSON)
61    @Produces(MediaType.APPLICATION_JSON)
62    public Response editNote(NoteInfo text) {
63        // tag::clientUsed3[]
64        if (gc.editNote(text.getHostname(), text.getText())) {
65        // end::clientUsed3[]
66            return Response.ok().build();
67        } else {
68            return Response.serverError().build();
69        }
70    }
71}

The QueryResource class uses the GraphQlClient interface to make requests to the graphql microservice and display the results. In a real application, you would make requests to an external GraphQL service, and you might do further manipulation of the data after retrieval.

The TypesafeGraphQLClientBuilder class creates a client object that implements the GraphQlClient interface and can interact with the graphql microservice. The GraphQlClient client can make requests to the URL specified by the graphql.server variable in the server.xml file. The client is used in the querySystem(), querySystemLoad(), and editNote() methods.

Add the SmallRye GraphQL client dependency to the project configuration file.

Replace the Maven project file.
query/pom.xml

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0"
  3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5    <modelVersion>4.0.0</modelVersion>
  6
  7    <groupId>io.openliberty.guides</groupId>
  8    <artifactId>guide-graphql-client-query</artifactId>
  9    <version>1.0-SNAPSHOT</version>
 10    <packaging>war</packaging>
 11
 12    <properties>
 13        <maven.compiler.source>1.8</maven.compiler.source>
 14        <maven.compiler.target>1.8</maven.compiler.target>
 15        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 16        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 17        <!-- Liberty configuration -->
 18        <liberty.var.default.http.port>9084</liberty.var.default.http.port>
 19        <liberty.var.default.https.port>9447</liberty.var.default.https.port>
 20    </properties>
 21
 22    <dependencies>
 23        <!-- Provided dependencies -->
 24        <dependency>
 25            <groupId>jakarta.platform</groupId>
 26            <artifactId>jakarta.jakartaee-api</artifactId>
 27            <version>9.1.0</version>
 28            <scope>provided</scope>
 29        </dependency>
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>5.0</version>
 34            <type>pom</type>
 35            <scope>provided</scope>
 36        </dependency>
 37        
 38        <!-- Required dependencies -->
 39        <!-- tag::models[] -->
 40        <dependency>
 41           <groupId>io.openliberty.guides</groupId>
 42           <artifactId>guide-graphql-client-models</artifactId>
 43           <version>1.0-SNAPSHOT</version>
 44        </dependency>
 45        <!-- end::models[] -->
 46        
 47        <!-- GraphQL API dependencies -->
 48        <!-- tag::clientDependenies[] -->
 49        <dependency>
 50            <groupId>io.smallrye</groupId>
 51            <artifactId>smallrye-graphql-client</artifactId>
 52            <version>2.0.0.RC4</version>
 53        </dependency>
 54        <dependency>
 55            <groupId>io.smallrye</groupId>
 56            <artifactId>smallrye-graphql-client-implementation-vertx</artifactId>
 57            <version>2.0.0.RC4</version>
 58        </dependency>
 59        <dependency>
 60            <groupId>io.smallrye.stork</groupId>
 61            <artifactId>stork-core</artifactId>
 62            <version>1.1.2</version>
 63        </dependency>
 64        <dependency>
 65            <groupId>org.slf4j</groupId>
 66            <artifactId>slf4j-simple</artifactId>
 67            <version>1.7.30</version>
 68        </dependency>
 69        <!-- end::clientDependenies[] -->
 70             
 71        <!-- For tests -->
 72        <!-- tag::testDependenies[] -->
 73        <dependency>
 74            <groupId>org.junit.jupiter</groupId>
 75            <artifactId>junit-jupiter</artifactId>
 76            <version>5.8.2</version>
 77            <scope>test</scope>
 78        </dependency>
 79        <dependency>
 80            <groupId>org.jboss.resteasy</groupId>
 81            <artifactId>resteasy-client</artifactId>
 82            <version>6.0.0.Final</version>
 83            <scope>test</scope>
 84        </dependency>
 85        <dependency>
 86            <groupId>org.jboss.resteasy</groupId>
 87            <artifactId>resteasy-json-binding-provider</artifactId>
 88            <version>6.0.0.Final</version>
 89            <scope>test</scope>
 90        </dependency>
 91        <dependency>
 92            <groupId>org.glassfish</groupId>
 93            <artifactId>jakarta.json</artifactId>
 94            <version>2.0.1</version>
 95            <scope>test</scope>
 96        </dependency>
 97        <dependency>
 98            <groupId>org.testcontainers</groupId>
 99            <artifactId>testcontainers</artifactId>
100            <version>1.16.3</version>
101            <scope>test</scope>
102        </dependency>
103        <dependency>
104            <groupId>org.testcontainers</groupId>
105            <artifactId>junit-jupiter</artifactId>
106            <version>1.16.3</version>
107            <scope>test</scope>
108        </dependency>
109        <dependency>
110            <groupId>org.slf4j</groupId>
111            <artifactId>slf4j-log4j12</artifactId>
112            <version>1.7.36</version>
113            <scope>test</scope>
114        </dependency>
115        <!-- end::testDependenies[] -->
116    </dependencies>
117
118    <build>
119        <finalName>${project.artifactId}</finalName>
120        <plugins>
121            <!-- Enable liberty-maven plugin -->
122            <plugin>
123                <groupId>io.openliberty.tools</groupId>
124                <artifactId>liberty-maven-plugin</artifactId>
125                <version>3.5.2</version>
126            </plugin>
127            <plugin>
128                <groupId>org.apache.maven.plugins</groupId>
129                <artifactId>maven-war-plugin</artifactId>
130                <version>3.3.1</version>
131            </plugin>
132            <plugin>
133                <groupId>org.apache.maven.plugins</groupId>
134                <artifactId>maven-surefire-plugin</artifactId>
135                <version>2.22.2</version>
136            </plugin>
137            <!-- tag::failsafe[] -->
138            <plugin>
139                <groupId>org.apache.maven.plugins</groupId>
140                <artifactId>maven-failsafe-plugin</artifactId>
141                <version>2.22.0</version>
142                <configuration>
143                    <systemPropertyVariables>
144                        <http.port>${liberty.var.default.http.port}</http.port>
145                    </systemPropertyVariables>
146                </configuration>
147                <executions>
148                    <execution>
149                        <goals>
150                            <goal>integration-test</goal>
151                            <goal>verify</goal>
152                        </goals>
153                    </execution>
154                </executions>
155            </plugin>
156            <!-- end::failsafe[] -->
157        </plugins>
158    </build>
159</project>

The smallrye-graphql-client dependencies provide the classes that you use to interact with a graphql microservice.

To run the service, you must correctly configure the Liberty server.

Replace the server configuration file.
query/src/main/liberty/config/server.xml

server.xml

 1<server description="Query Service">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.0</feature>
 5    <feature>cdi-3.0</feature>
 6    <feature>jsonb-2.0</feature>
 7    <feature>mpConfig-3.0</feature>
 8    <feature>mpOpenAPI-3.0</feature>
 9  </featureManager>
10
11  <variable name="default.http.port" defaultValue="9084"/>
12  <variable name="default.https.port" defaultValue="9447"/>
13  <!-- tag::url[] -->
14  <variable name="graphql.server" defaultValue="http://graphql:9082/graphql"/>
15  <!-- end::url[] -->
16  
17  <httpEndpoint host="*" httpPort="${default.http.port}"
18      httpsPort="${default.https.port}" id="defaultHttpEndpoint"/>
19
20  <webApplication location="guide-graphql-client-query.war" contextRoot="/"/>
21</server>

The graphql.server variable is defined in the server.xml file. This variable defines where the GraphQL client makes requests to.

Building and running the application

From the start directory, run the following commands:

mvn -pl models install
mvn package

The mvn install command compiles and packages the object types you created to a .jar file. This allows them to be used by the system and graphql services. The mvn package command packages the system, graphql, and query services to .war files.

Run the following command to download or update to the latest Open Liberty Docker image:

docker pull icr.io/appcafe/open-liberty:full-java11-openj9-ubi

Dockerfiles are already set up for you. Build your Docker images with the following commands:

docker build -t system:1.0-java8-SNAPSHOT --build-arg JAVA_VERSION=java8 system/.
docker build -t system:1.0-java11-SNAPSHOT --build-arg JAVA_VERSION=java11 system/.
docker build -t graphql:1.0-SNAPSHOT graphql/.
docker build -t query:1.0-SNAPSHOT query/.

Run these Docker images using the provided startContainers script. The script creates a network for the services to communicate through. It creates the two system microservices, a graphql microservice, and a query microservice that interact with each other.

.\scripts\startContainers.bat
./scripts/startContainers.sh

The containers might take some time to become available.

Accessing the application

To access the client service, visit the http://localhost:9084/openapi/ui/ URL. This URL displays the available REST endpoints that test the API endpoints that you created.

Try the query operations

From the OpenAPI UI, test the read operation at the GET /query/system/{hostname} endpoint. This request retrieves the system properties for the hostname specified. The following example shows what happens when the hostname is set to system-java8, but you can try out the operations using the hostname system-java11 as well:

{
  "hostname": "system-java8",
  "java": {
    "vendor": "International Business Machines Corporation",
    "version": "1.8.0_312"
  },
  "osArch": "amd64",
  "osName": "Linux",
  "osVersion": "5.10.25-linuxkit",
  "systemMetrics": {
    "heapSize": 2086993920,
    "nonHeapSize": -1,
    "processors": 8
  },
  "username": "default"
}

You can retrieve the information about the resource usage of any number of system services at the GET /query/systemLoad/{hostnames} endpoint. The following example shows what happens when the hostnames are set to system-java8,system-java11:

[
  {
    "hostname": "system-java8",
    "loadData": {
      "heapUsed": 34251904,
      "loadAverage": 0.11,
      "nonHeapUsed": 84034688
    }
  },
  {
    "hostname": "system-java11",
    "loadData": {
      "heapUsed": 41953280,
      "loadAverage": 0.11,
      "nonHeapUsed": 112506520
    }
  }
]

Try the mutation operation

You can also make requests to add a note to a system service at the POST /query/mutation/system/note endpoint. To add a note to the system service running on Java 8, specify the following in the request body:

{
  "hostname": "system-java8",
  "text": "I am trying out GraphQL on Open Liberty!"
}

You will receive a 200 response code if the request is processed succesfully.

You can see the note you added to the system service at the GET /query/system/{hostname} endpoint.

Tearing down the environment

When you’re done checking out the application, run the following script to stop the application:

.\scripts\stopContainers.bat
./scripts/stopContainers.sh

Testing the application

Although you can test your application manually, you should rely on automated tests. In this section, you’ll create integration tests using Testcontainers to verify that the basic operations you implemented function correctly.

First, create a RESTful client interface for the query microservice.

Create the QueryResourceClient.java interface.
query/src/test/java/it/io/openliberty/guides/query/QueryResourceClient.java

QueryResourceClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package it.io.openliberty.guides.query;
14
15import java.util.List;
16import jakarta.enterprise.context.ApplicationScoped;
17import jakarta.ws.rs.Consumes;
18import jakarta.ws.rs.GET;
19import jakarta.ws.rs.POST;
20import jakarta.ws.rs.Path;
21import jakarta.ws.rs.PathParam;
22import jakarta.ws.rs.Produces;
23import jakarta.ws.rs.core.MediaType;
24import jakarta.ws.rs.core.Response;
25
26import io.openliberty.guides.graphql.models.SystemInfo;
27import io.openliberty.guides.graphql.models.SystemLoad;
28import io.openliberty.guides.graphql.models.NoteInfo;
29
30@ApplicationScoped
31@Path("query")
32public interface QueryResourceClient {
33
34    // tag::querySystem[]
35    @GET
36    @Path("system/{hostname}")
37    @Produces(MediaType.APPLICATION_JSON)
38    SystemInfo querySystem(@PathParam("hostname") String hostname);
39    // end::querySystem[]
40
41    // tag::querySystemLoad[]
42    @GET
43    @Path("systemLoad/{hostnames}")
44    @Produces(MediaType.APPLICATION_JSON)
45    List<SystemLoad> querySystemLoad(@PathParam("hostnames") String hostnames);
46    // end::querySystemLoad[]
47
48    // tag::editNote[]
49    @POST
50    @Path("mutation/system/note")
51    @Consumes(MediaType.APPLICATION_JSON)
52    @Produces(MediaType.APPLICATION_JSON)
53    Response editNote(NoteInfo text);
54    // end::editNote[]
55}

This interface declares querySystem(), querySystemLoad(), and editNote() methods for accessing each of the endpoints that are set up to access the query microservice.

Create the test container class that accesses the query image that you built in previous section.

Create the LibertyContainer.java file.
query/src/test/java/it/io/openliberty/guides/query/LibertyContainer.java

LibertyContainer.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package it.io.openliberty.guides.query;
14
15// imports for a JAXRS client to simplify the code
16import org.jboss.resteasy.client.jaxrs.ResteasyClient;
17import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
18import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
19// logger imports
20import org.slf4j.Logger;
21import org.slf4j.LoggerFactory;
22// testcontainers imports
23import org.testcontainers.containers.GenericContainer;
24import org.testcontainers.containers.wait.strategy.Wait;
25
26import jakarta.ws.rs.client.ClientBuilder;
27import jakarta.ws.rs.core.UriBuilder;
28
29public class LibertyContainer extends GenericContainer<LibertyContainer> {
30
31    static final Logger LOGGER = LoggerFactory.getLogger(LibertyContainer.class);
32    private String baseURL;
33
34    public LibertyContainer(final String dockerImageName) {
35        super(dockerImageName);
36        // wait for smarter planet message by default
37        waitingFor(Wait.forLogMessage("^.*CWWKF0011I.*$", 1));
38        this.addExposedPorts(9084);
39        return;
40    }
41
42    // tag::createRestClient[]
43    public <T> T createRestClient(Class<T> clazz) {
44        String urlPath = getBaseURL();
45        ClientBuilder builder = ResteasyClientBuilder.newBuilder();
46        ResteasyClient client = (ResteasyClient) builder.build();
47        ResteasyWebTarget target = client.target(UriBuilder.fromPath(urlPath));
48        return target.proxy(clazz);
49    }
50    // end::createRestClient[]
51
52    // tag::getBaseURL[]
53    public String getBaseURL() throws IllegalStateException {
54        if (baseURL != null) {
55            return baseURL;
56        }
57        if (!this.isRunning()) {
58            throw new IllegalStateException(
59                "Container must be running to determine hostname and port");
60        }
61        baseURL =  "http://" + this.getContainerIpAddress()
62            + ":" + this.getFirstMappedPort();
63        System.out.println("TEST: " + baseURL);
64        return baseURL;
65    }
66    // end::getBaseURL[]
67}

The createRestClient() method creates a REST client instance with the QueryResourceClient interface. The getBaseURL() method constructs the URL that can access the query image.

Now, create your integration test cases.

Create the QueryResourceIT.java file.
query/src/test/java/it/io/openliberty/guides/query/QueryResourceIT.java

QueryResourceIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package it.io.openliberty.guides.query;
 14
 15import static org.junit.jupiter.api.Assertions.assertEquals;
 16import static org.junit.jupiter.api.Assertions.assertNotNull;
 17
 18import org.junit.jupiter.api.BeforeAll;
 19import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
 20import org.junit.jupiter.api.Order;
 21import org.junit.jupiter.api.Test;
 22import org.junit.jupiter.api.TestMethodOrder;
 23import org.slf4j.Logger;
 24import org.slf4j.LoggerFactory;
 25import org.testcontainers.containers.GenericContainer;
 26import org.testcontainers.containers.Network;
 27import org.testcontainers.containers.output.Slf4jLogConsumer;
 28import org.testcontainers.junit.jupiter.Container;
 29import org.testcontainers.junit.jupiter.Testcontainers;
 30
 31import java.util.List;
 32import jakarta.ws.rs.core.Response;
 33import io.openliberty.guides.graphql.models.NoteInfo;
 34import io.openliberty.guides.graphql.models.SystemLoad;
 35import io.openliberty.guides.graphql.models.SystemLoadData;
 36import io.openliberty.guides.graphql.models.SystemInfo;
 37
 38// tag::testcontainers[]
 39@Testcontainers
 40// end::testcontainers[]
 41@TestMethodOrder(OrderAnnotation.class)
 42public class QueryResourceIT {
 43
 44    private static Logger logger = LoggerFactory.getLogger(QueryResourceIT.class);
 45    private static String system8ImageName = "system:1.0-java8-SNAPSHOT";
 46    private static String queryImageName = "query:1.0-SNAPSHOT";
 47    private static String graphqlImageName = "graphql:1.0-SNAPSHOT";
 48
 49    public static QueryResourceClient client;
 50    public static Network network = Network.newNetwork();
 51
 52    // tag::systemContainer[]
 53    // tag::container[]
 54    @Container
 55    // end::container[]
 56    public static GenericContainer<?> systemContainer
 57        = new GenericContainer<>(system8ImageName)
 58              .withNetwork(network)
 59              .withExposedPorts(9080)
 60              .withNetworkAliases("system-java8")
 61              .withLogConsumer(new Slf4jLogConsumer(logger));
 62    // end::systemContainer[]
 63
 64    // tag::graphqlContainer[]
 65    @Container
 66    public static LibertyContainer graphqlContainer
 67        = new LibertyContainer(graphqlImageName)
 68              .withNetwork(network)
 69              .withExposedPorts(9082)
 70              .withNetworkAliases("graphql")
 71              .withLogConsumer(new Slf4jLogConsumer(logger));
 72    // end::graphqlContainer[]
 73
 74    // tag::libertyContainer[]
 75    @Container
 76    public static LibertyContainer libertyContainer
 77        = new LibertyContainer(queryImageName)
 78              .withNetwork(network)
 79              .withExposedPorts(9084)
 80              .withLogConsumer(new Slf4jLogConsumer(logger));
 81    // end::libertyContainer[]
 82
 83    @BeforeAll
 84    public static void setupTestClass() throws Exception {
 85        System.out.println("TEST: Starting Liberty Container setup");
 86        client = libertyContainer.createRestClient(QueryResourceClient.class);
 87    }
 88
 89    // tag::testGetSystem[]
 90    @Test
 91    @Order(1)
 92    public void testGetSystem() {
 93        System.out.println("TEST: Testing get system /system/system-java8");
 94        SystemInfo systemInfo = client.querySystem("system-java8");
 95        assertEquals(systemInfo.getHostname(), "system-java8");
 96        assertNotNull(systemInfo.getOsVersion(), "osVersion is null");
 97        assertNotNull(systemInfo.getJava(), "java is null");
 98        assertNotNull(systemInfo.getSystemMetrics(), "systemMetrics is null");
 99    }
100    // end::testGetSystem[]
101
102    // tag::testGetSystemLoad[]
103    @Test
104    @Order(2)
105    public void testGetSystemLoad() {
106        System.out.println("TEST: Testing get system load /systemLoad/system-java8");
107        List<SystemLoad> systemLoad = client.querySystemLoad("system-java8");
108        assertEquals(systemLoad.get(0).getHostname(), "system-java8");
109        SystemLoadData systemLoadData = systemLoad.get(0).getLoadData();
110        assertNotNull(systemLoadData.getLoadAverage(), "loadAverage is null");
111        assertNotNull(systemLoadData.getHeapUsed(), "headUsed is null");
112        assertNotNull(systemLoadData.getNonHeapUsed(), "nonHeapUsed is null");
113    }
114    // end::testGetSystemLoad[]
115
116    // tag::testEditNote[]
117    @Test
118    @Order(3)
119    public void testEditNote() {
120        System.out.println("TEST: Testing editing note /mutation/system/note");
121        NoteInfo note = new NoteInfo();
122        note.setHostname("system-java8");
123        note.setText("I am trying out GraphQL on Open Liberty!");
124        Response response = client.editNote(note);
125        assertEquals(200, response.getStatus(), "Incorrect response code");
126        SystemInfo systemInfo = client.querySystem("system-java8");
127        assertEquals(systemInfo.getNote(), "I am trying out GraphQL on Open Liberty!");
128    }
129    // end::testEditNote[]
130}

Define the systemContainer test container to start up the system-java8 image, the graphqlContainer test container to start up the graphql image, and the libertyContainer test container to start up the query image. Make sure that the containers use the same network.

The @Testcontainers annotation finds all fields that are annotated with the @Container annotation and calls their container lifecycle methods. The static function declaration on each container indicates that this container will be started only once before any test method is executed and stopped after the last test method is executed.

The testGetSystem() verifies the /query/system/{hostname} endpoint with hostname set to system-java8.

The testGetSystemLoad() verifies the /query/systemLoad/{hostnames} endpoint with hostnames set to system-java8.

The testEditNote() verifies the mutation operation at the /query/mutation/system/note endpoint.

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0"
  3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5    <modelVersion>4.0.0</modelVersion>
  6
  7    <groupId>io.openliberty.guides</groupId>
  8    <artifactId>guide-graphql-client-query</artifactId>
  9    <version>1.0-SNAPSHOT</version>
 10    <packaging>war</packaging>
 11
 12    <properties>
 13        <maven.compiler.source>1.8</maven.compiler.source>
 14        <maven.compiler.target>1.8</maven.compiler.target>
 15        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 16        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 17        <!-- Liberty configuration -->
 18        <liberty.var.default.http.port>9084</liberty.var.default.http.port>
 19        <liberty.var.default.https.port>9447</liberty.var.default.https.port>
 20    </properties>
 21
 22    <dependencies>
 23        <!-- Provided dependencies -->
 24        <dependency>
 25            <groupId>jakarta.platform</groupId>
 26            <artifactId>jakarta.jakartaee-api</artifactId>
 27            <version>9.1.0</version>
 28            <scope>provided</scope>
 29        </dependency>
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>5.0</version>
 34            <type>pom</type>
 35            <scope>provided</scope>
 36        </dependency>
 37        
 38        <!-- Required dependencies -->
 39        <!-- tag::models[] -->
 40        <dependency>
 41           <groupId>io.openliberty.guides</groupId>
 42           <artifactId>guide-graphql-client-models</artifactId>
 43           <version>1.0-SNAPSHOT</version>
 44        </dependency>
 45        <!-- end::models[] -->
 46        
 47        <!-- GraphQL API dependencies -->
 48        <!-- tag::clientDependenies[] -->
 49        <dependency>
 50            <groupId>io.smallrye</groupId>
 51            <artifactId>smallrye-graphql-client</artifactId>
 52            <version>2.0.0.RC4</version>
 53        </dependency>
 54        <dependency>
 55            <groupId>io.smallrye</groupId>
 56            <artifactId>smallrye-graphql-client-implementation-vertx</artifactId>
 57            <version>2.0.0.RC4</version>
 58        </dependency>
 59        <dependency>
 60            <groupId>io.smallrye.stork</groupId>
 61            <artifactId>stork-core</artifactId>
 62            <version>1.1.2</version>
 63        </dependency>
 64        <dependency>
 65            <groupId>org.slf4j</groupId>
 66            <artifactId>slf4j-simple</artifactId>
 67            <version>1.7.30</version>
 68        </dependency>
 69        <!-- end::clientDependenies[] -->
 70             
 71        <!-- For tests -->
 72        <!-- tag::testDependenies[] -->
 73        <dependency>
 74            <groupId>org.junit.jupiter</groupId>
 75            <artifactId>junit-jupiter</artifactId>
 76            <version>5.8.2</version>
 77            <scope>test</scope>
 78        </dependency>
 79        <dependency>
 80            <groupId>org.jboss.resteasy</groupId>
 81            <artifactId>resteasy-client</artifactId>
 82            <version>6.0.0.Final</version>
 83            <scope>test</scope>
 84        </dependency>
 85        <dependency>
 86            <groupId>org.jboss.resteasy</groupId>
 87            <artifactId>resteasy-json-binding-provider</artifactId>
 88            <version>6.0.0.Final</version>
 89            <scope>test</scope>
 90        </dependency>
 91        <dependency>
 92            <groupId>org.glassfish</groupId>
 93            <artifactId>jakarta.json</artifactId>
 94            <version>2.0.1</version>
 95            <scope>test</scope>
 96        </dependency>
 97        <dependency>
 98            <groupId>org.testcontainers</groupId>
 99            <artifactId>testcontainers</artifactId>
100            <version>1.16.3</version>
101            <scope>test</scope>
102        </dependency>
103        <dependency>
104            <groupId>org.testcontainers</groupId>
105            <artifactId>junit-jupiter</artifactId>
106            <version>1.16.3</version>
107            <scope>test</scope>
108        </dependency>
109        <dependency>
110            <groupId>org.slf4j</groupId>
111            <artifactId>slf4j-log4j12</artifactId>
112            <version>1.7.36</version>
113            <scope>test</scope>
114        </dependency>
115        <!-- end::testDependenies[] -->
116    </dependencies>
117
118    <build>
119        <finalName>${project.artifactId}</finalName>
120        <plugins>
121            <!-- Enable liberty-maven plugin -->
122            <plugin>
123                <groupId>io.openliberty.tools</groupId>
124                <artifactId>liberty-maven-plugin</artifactId>
125                <version>3.5.2</version>
126            </plugin>
127            <plugin>
128                <groupId>org.apache.maven.plugins</groupId>
129                <artifactId>maven-war-plugin</artifactId>
130                <version>3.3.1</version>
131            </plugin>
132            <plugin>
133                <groupId>org.apache.maven.plugins</groupId>
134                <artifactId>maven-surefire-plugin</artifactId>
135                <version>2.22.2</version>
136            </plugin>
137            <!-- tag::failsafe[] -->
138            <plugin>
139                <groupId>org.apache.maven.plugins</groupId>
140                <artifactId>maven-failsafe-plugin</artifactId>
141                <version>2.22.0</version>
142                <configuration>
143                    <systemPropertyVariables>
144                        <http.port>${liberty.var.default.http.port}</http.port>
145                    </systemPropertyVariables>
146                </configuration>
147                <executions>
148                    <execution>
149                        <goals>
150                            <goal>integration-test</goal>
151                            <goal>verify</goal>
152                        </goals>
153                    </execution>
154                </executions>
155            </plugin>
156            <!-- end::failsafe[] -->
157        </plugins>
158    </build>
159</project>

The required dependencies are already added to the pom.xml Maven configuration file for you, including JUnit5, JBoss RESTEasy client, Glassfish JSON, Testcontainers, and Log4J libraries.

To enable running the integration test by the Maven verify goal, the maven-failsafe-plugin plugin is also required.

Running the tests

You can run the Maven verify goal, which compiles the java files, starts the containers, runs the tests, and then stops the containers.

mvn verify

You will see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.query.QueryResourceIT
...
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 11.694 s - in it.io.openliberty.guides.query.QueryResourceIT

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

Great work! You’re done!

You just learnt how to use a GraphQL client to run GraphQL queries and mutations!

Guide Attribution

Running GraphQL queries and mutations using a GraphQL client by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star