Running GraphQL queries and mutations using a GraphQL client

duration 20 minutes

Prerequisites:

Learn how to use the SmallRye GraphQL client’s typesafe interface to query and mutate data from multiple microservices.

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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.query.client;
13
14import org.eclipse.microprofile.graphql.Query;
15import org.eclipse.microprofile.graphql.Mutation;
16import org.eclipse.microprofile.graphql.Name;
17
18import io.openliberty.guides.graphql.models.SystemInfo;
19import io.openliberty.guides.graphql.models.SystemLoad;
20import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
21
22// tag::clientApi[]
23@GraphQLClientApi
24// end::clientApi[]
25public interface GraphQlClient {
26    // tag::querySystemTag[]
27    @Query
28    // end::querySystemTag[]
29    // tag::systemInfo[]
30    SystemInfo system(@Name("hostname") String hostname);
31    // end::systemInfo[]
32
33    // tag::querySystemLoadTag[]
34    @Query("systemLoad")
35    // end::querySystemLoadTag[]
36    // tag::systemLoad[]
37    SystemLoad[] getSystemLoad(@Name("hostnames") String[] hostnames);
38    // end::systemLoad[]
39
40    // tag::mutationTag[]
41    @Mutation
42    // end::mutationTag[]
43    // tag::editNote[]
44    boolean editNote(@Name("hostname") String host, @Name("note") String note);
45    // end::editNote[]
46
47}

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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.query;
13
14import io.openliberty.guides.graphql.models.NoteInfo;
15import io.openliberty.guides.graphql.models.SystemInfo;
16import io.openliberty.guides.graphql.models.SystemLoad;
17import io.openliberty.guides.query.client.GraphQlClient;
18import io.smallrye.graphql.client.typesafe.api.TypesafeGraphQLClientBuilder;
19import jakarta.enterprise.context.ApplicationScoped;
20import jakarta.ws.rs.Consumes;
21import jakarta.ws.rs.GET;
22import jakarta.ws.rs.POST;
23import jakarta.ws.rs.Path;
24import jakarta.ws.rs.PathParam;
25import jakarta.ws.rs.Produces;
26import jakarta.ws.rs.core.MediaType;
27import jakarta.ws.rs.core.Response;
28
29@ApplicationScoped
30@Path("query")
31public class QueryResource {
32
33    // tag::clientBuilder[]
34    private GraphQlClient gc = TypesafeGraphQLClientBuilder.newBuilder()
35                                                   .build(GraphQlClient.class);
36    // end::clientBuilder[]
37
38    @GET
39    @Path("system/{hostname}")
40    @Produces(MediaType.APPLICATION_JSON)
41    public SystemInfo querySystem(@PathParam("hostname") String hostname) {
42        // tag::clientUsed1[]
43        return gc.system(hostname);
44        // end::clientUsed1[]
45    }
46
47    @GET
48    @Path("systemLoad/{hostnames}")
49    @Produces(MediaType.APPLICATION_JSON)
50    public SystemLoad[] querySystemLoad(@PathParam("hostnames") String hostnames) {
51        String[] hostnameArray = hostnames.split(",");
52        // tag::clientUsed2[]
53        return gc.getSystemLoad(hostnameArray);
54        // end::clientUsed2[]
55    }
56
57    @POST
58    @Path("mutation/system/note")
59    @Consumes(MediaType.APPLICATION_JSON)
60    @Produces(MediaType.APPLICATION_JSON)
61    public Response editNote(NoteInfo text) {
62        // tag::clientUsed3[]
63        if (gc.editNote(text.getHostname(), text.getText())) {
64        // end::clientUsed3[]
65            return Response.ok().build();
66        } else {
67            return Response.serverError().build();
68        }
69    }
70}

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>11</maven.compiler.source>
 14        <maven.compiler.target>11</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.http.port>9084</liberty.var.http.port>
 19        <liberty.var.https.port>9447</liberty.var.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>10.0.0</version>
 28            <scope>provided</scope>
 29        </dependency>
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>6.1</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.11.0</version>
 53        </dependency>
 54        <dependency>
 55            <groupId>io.smallrye</groupId>
 56            <artifactId>smallrye-graphql-client-implementation-vertx</artifactId>
 57            <version>2.11.0</version>
 58        </dependency>
 59        <dependency>
 60            <groupId>io.smallrye.stork</groupId>
 61            <artifactId>stork-core</artifactId>
 62            <version>2.7.1</version>
 63        </dependency>
 64        <dependency>
 65            <groupId>org.slf4j</groupId>
 66            <artifactId>slf4j-simple</artifactId>
 67            <version>2.0.16</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.11.3</version>
 77            <scope>test</scope>
 78        </dependency>
 79        <dependency>
 80            <groupId>org.jboss.resteasy</groupId>
 81            <artifactId>resteasy-client</artifactId>
 82            <version>6.2.11.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.2.11.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.20.4</version>
101            <scope>test</scope>
102        </dependency>
103        <dependency>
104            <groupId>org.testcontainers</groupId>
105            <artifactId>junit-jupiter</artifactId>
106            <version>1.20.4</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.11.1</version>
126            </plugin>
127            <plugin>
128                <groupId>org.apache.maven.plugins</groupId>
129                <artifactId>maven-war-plugin</artifactId>
130                <version>3.4.0</version>
131            </plugin>
132            <plugin>
133                <groupId>org.apache.maven.plugins</groupId>
134                <artifactId>maven-surefire-plugin</artifactId>
135                <version>3.5.2</version>
136            </plugin>
137            <!-- tag::failsafe[] -->
138            <plugin>
139                <groupId>org.apache.maven.plugins</groupId>
140                <artifactId>maven-failsafe-plugin</artifactId>
141                <version>3.5.2</version>
142                <configuration>
143                    <systemPropertyVariables>
144                        <http.port>${liberty.var.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.

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

server.xml

 1<server description="Query Service">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>cdi-4.0</feature>
 6    <feature>jsonb-3.0</feature>
 7    <feature>mpConfig-3.1</feature>
 8    <feature>mpOpenAPI-3.1</feature>
 9  </featureManager>
10
11  <variable name="http.port" defaultValue="9084"/>
12  <variable name="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="${http.port}"
18      httpsPort="${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.

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

docker build -t system:1.0-java11-SNAPSHOT --build-arg JAVA_VERSION=java11 system/.
docker build -t system:1.0-java17-SNAPSHOT --build-arg JAVA_VERSION=java17 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-java11, but you can try out the operations using the hostname system-java17 as well:

{
  "hostname": "system-java11",
  "java": {
    "vendor": "IBM Corporation",
    "version": "11.0.18"
  },
  "osArch": "amd64",
  "osName": "Linux",
  "osVersion": "5.15.0-67-generic",
  "systemMetrics": {
    "heapSize": 536870912,
    "nonHeapSize": -1,
    "processors": 2
  },
  "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-java11,system-java17:

[
  {
    "hostname": "system-java11",
    "loadData": {
      "heapUsed": 30090920,
      "loadAverage": 0.08,
      "nonHeapUsed": 87825316
    }
  },
  {
    "hostname": "system-java17",
    "loadData": {
      "heapUsed": 39842888,
      "loadAverage": 0.08,
      "nonHeapUsed": 93098960
    }
  }
]

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 11, specify the following in the request body:

{
  "hostname": "system-java11",
  "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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package it.io.openliberty.guides.query;
13
14import java.util.List;
15import jakarta.enterprise.context.ApplicationScoped;
16import jakarta.ws.rs.Consumes;
17import jakarta.ws.rs.GET;
18import jakarta.ws.rs.POST;
19import jakarta.ws.rs.Path;
20import jakarta.ws.rs.PathParam;
21import jakarta.ws.rs.Produces;
22import jakarta.ws.rs.core.MediaType;
23import jakarta.ws.rs.core.Response;
24
25import io.openliberty.guides.graphql.models.SystemInfo;
26import io.openliberty.guides.graphql.models.SystemLoad;
27import io.openliberty.guides.graphql.models.NoteInfo;
28
29@ApplicationScoped
30@Path("query")
31public interface QueryResourceClient {
32
33    // tag::querySystem[]
34    @GET
35    @Path("system/{hostname}")
36    @Produces(MediaType.APPLICATION_JSON)
37    SystemInfo querySystem(@PathParam("hostname") String hostname);
38    // end::querySystem[]
39
40    // tag::querySystemLoad[]
41    @GET
42    @Path("systemLoad/{hostnames}")
43    @Produces(MediaType.APPLICATION_JSON)
44    List<SystemLoad> querySystemLoad(@PathParam("hostnames") String hostnames);
45    // end::querySystemLoad[]
46
47    // tag::editNote[]
48    @POST
49    @Path("mutation/system/note")
50    @Consumes(MediaType.APPLICATION_JSON)
51    @Produces(MediaType.APPLICATION_JSON)
52    Response editNote(NoteInfo text);
53    // end::editNote[]
54}

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

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

Define the systemContainer test container to start up the system-java11 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-java11.

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

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>11</maven.compiler.source>
 14        <maven.compiler.target>11</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.http.port>9084</liberty.var.http.port>
 19        <liberty.var.https.port>9447</liberty.var.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>10.0.0</version>
 28            <scope>provided</scope>
 29        </dependency>
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>6.1</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.11.0</version>
 53        </dependency>
 54        <dependency>
 55            <groupId>io.smallrye</groupId>
 56            <artifactId>smallrye-graphql-client-implementation-vertx</artifactId>
 57            <version>2.11.0</version>
 58        </dependency>
 59        <dependency>
 60            <groupId>io.smallrye.stork</groupId>
 61            <artifactId>stork-core</artifactId>
 62            <version>2.7.1</version>
 63        </dependency>
 64        <dependency>
 65            <groupId>org.slf4j</groupId>
 66            <artifactId>slf4j-simple</artifactId>
 67            <version>2.0.16</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.11.3</version>
 77            <scope>test</scope>
 78        </dependency>
 79        <dependency>
 80            <groupId>org.jboss.resteasy</groupId>
 81            <artifactId>resteasy-client</artifactId>
 82            <version>6.2.11.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.2.11.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.20.4</version>
101            <scope>test</scope>
102        </dependency>
103        <dependency>
104            <groupId>org.testcontainers</groupId>
105            <artifactId>junit-jupiter</artifactId>
106            <version>1.20.4</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.11.1</version>
126            </plugin>
127            <plugin>
128                <groupId>org.apache.maven.plugins</groupId>
129                <artifactId>maven-war-plugin</artifactId>
130                <version>3.4.0</version>
131            </plugin>
132            <plugin>
133                <groupId>org.apache.maven.plugins</groupId>
134                <artifactId>maven-surefire-plugin</artifactId>
135                <version>3.5.2</version>
136            </plugin>
137            <!-- tag::failsafe[] -->
138            <plugin>
139                <groupId>org.apache.maven.plugins</groupId>
140                <artifactId>maven-failsafe-plugin</artifactId>
141                <version>3.5.2</version>
142                <configuration>
143                    <systemPropertyVariables>
144                        <http.port>${liberty.var.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

Copy file contents
Copied to clipboard

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