Integrate Wiremock and Quarkus in Testing for Mocking API Response

There are multipe ways of testing API connectivity from one service to another, in Integration Testing we can do direct connectivity or using an external API mocking such as Microcks. But for a simple Unit Testing, we can leverage Wiremock to do this.

And in this writings, i will try to integrate Wiremock with Quarkus for Unit Testing. First, lets start with a simple Maven pom file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>quarkus-and-wiremock</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <compiler-plugin.version>3.11.0</compiler-plugin.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <skipITs>true</skipITs>
        <surefire-plugin.version>3.1.2</surefire-plugin.version>

        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>com.redhat.quarkus.platform</quarkus.platform.group-id>
        <quarkus.platform.version>3.2.6.SP1-redhat-00001</quarkus.platform.version>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-bom</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-arc</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy</artifactId>
        </dependency>

        <!-- external call -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-client</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-client-jackson</artifactId>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.wiremock</groupId>
            <artifactId>wiremock</artifactId>
            <version>3.3.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Lets create a simple Rest API client,

package com.edw.client;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import java.util.HashMap;

@Path("/")
@RegisterRestClient
public interface MockyService {
    @GET
    @Path("/")
    HashMap getDefaultMockData();
}

Its configuration file,

# default
quarkus.http.port=8080
quarkus.log.level=INFO
quarkus.log.category."com.edw".level=DEBUG
quarkus.log.category."org.apache.http".level=INFO

# disable sending anonymous statistics
quarkus.analytics.disabled=true

quarkus.rest-client."com.edw.client.MockyService".url=https://run.mocky.io/v3/99687692-4390-4ca2-816a-35c015fd72d0

And lets call it from our Controller,

package com.edw.controller;

import com.edw.client.MockyService;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.util.HashMap;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/")
public class IndexController {

    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    @Inject
    @RestClient
    MockyService mockyService;

    @GET
    @Path("/call")
    @Produces(MediaType.APPLICATION_JSON)
    public Response callExternalUrl() {
        logger.debug("calling external-service");
        return Response
                .status(200)
                .entity(mockyService.getDefaultMockData())
                .build();
    }
}

We can do some curl test by hitting the endpoint directly thru a curl command,

$ curl -kv http://localhost:8080/call
*   Trying ::1:8080...
* TCP_NODELAY set
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /call HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< content-length: 17
<
* Connection #0 to host localhost left intact
{"hello":"world"}                                   

Now, lets try to do a unit testing to simulate this external API call. We can start by setting up a Wiremock server that will mocking a specific API endpoint

package com.edw.config;

import com.github.tomakehurst.wiremock.WireMockServer;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

import java.util.HashMap;
import java.util.Map;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

public class WiremockConfig implements QuarkusTestResourceLifecycleManager {
    private WireMockServer server;

    @Override
    public Map<String, String> start() {
        server = new WireMockServer(8082);
        server.start();
        server.stubFor(
                get(urlEqualTo("/"))
                        .willReturn(aResponse()
                                .withStatus(200)
                                .withHeader("Content-Type", "application/json")
                                .withBody("{\"hello\": \"mock\"}")));

        return new HashMap<>();
    }

    @Override
    public void stop() {
        if (server != null) {
            server.stop();
        }
    }
}

For testing, we create a new properties file which is pointint to our Wiremock server

# default
quarkus.log.level=INFO
quarkus.log.category."com.edw".level=DEBUG
quarkus.log.category."org.apache.http".level=DEBUG

# disable sending anonymous statistics
quarkus.analytics.disabled=true

quarkus.rest-client."com.edw.client.MockyService".url=http://localhost:8082

Lastly, lets create a new test case for this. Simulating a curl call to our endpoint

package com.edw.controller;

import com.edw.config.WiremockConfig;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

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

@QuarkusTest
@QuarkusTestResource(WiremockConfig.class)
public class IndexControllerTest {

    @Test
    public void testCall() {
        given()
            .when()
                .get("/call")
                .then()
            .statusCode(200)
                .body("hello", isA(String.class))
                .body("hello", equalTo("mock"))
            .log().all();
    }
}

The whole code for this post can be clone on below repository,

https://github.com/edwin/quarkus-and-wiremock

Deploying a Binary WAR File to Openshift 4 Using s2i

We can leverage s2i to deploy an existing war file into Openshift 4. How to do it is pretty much simple and straight forward, basically just running some oc command and you can have your war file deployed on Openshift.

But before we go far, we need to have a sample application to be deployed and for this sample, i have a hello world apps which is created by using Java Servlet.

https://github.com/edwin/jboss-eap-hello-world

We can clone this application, and build it using a simple maven command.

$ mvn clean package

Next step is create an empty folder where we would run our oc command,

$ mkdir -p jboss-eap/deployments

and we can put our ROOT.war file inside deployment folder,

$ tree .
.
+---- deployments
    +---- ROOT.war

Next is creating a new BuildConfig,

$ oc new-build --name=custom-eap-application \
	--binary=true --image-stream=jboss-eap74-openjdk11-openshift:latest

and start building it by using a start-build command, we can also repeat this step again if there is a new changes to our war file

$ oc start-build custom-eap-application --from-dir . --follow

and finally we can deploy our application directly by using new-app command,

$ oc new-app --name=custom-eap-application \ 
	--image-stream=edwin-ns/custom-eap-application:latest

So deployment can be done easily by using just 3 simple steps.

Create a Custom Route Certificate for Openshift 4

Sometimes we want to have a proper HTTPS certificate for our Openshift cluster, instead of a random Openshift generated certificate. We can do so by uploading our certificate into Openshift directly and completely replace default custom certificate.

But for this example, we are trying to generate a self-signed certificate with a custom attributes. We can start by generate a Root CA Key,

$ openssl genrsa -out rootCA.key 4096

After that we can create Root certificate based on previously generated rootCA.key

$ openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt

Next is uploading our rootCA into Openshift 4

$ oc create configmap custom-ca --from-file=ca-bundle.crt=rootCA.crt -n openshift-config

And update cluster-wide proxy configuration to use our custom root certificate

$ oc patch proxy/cluster \
     --type=merge \
     --patch='{"spec":{"trustedCA":{"name":"custom-ca"}}}'

Next is to generate a certificate dedicated for our Openshift, we can start by generating a certificate key

$ openssl genrsa -out localhost.key 2048

and use the corresponding key to generate certificate signing,

$ openssl req -new -key localhost.key -out localhost.csr

last is to generate certificate using our CA Root key and CSR file,

$ openssl x509 -req -in localhost.csr -CA rootCA.crt -CAkey rootCA.key \
	-CAcreateserial -out localhost.crt -days 1000 -sha256

We can verify the content of our CRT by using below command,

$ openssl x509 -in localhost.crt -text -noout

Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            01:d3:65:36:30:4a:81:54:7d:ab:96:a5:a8:62:f2:d0:23:da:e7:6e
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = ID, ST = Jakarta, L = Jakarta, O = RH, OU = GPS, CN = localhost, emailAddress = edwin@redhat.com
        Validity
            Not Before: Oct 16 06:50:00 2023 GMT
            Not After : Jul 12 06:50:00 2026 GMT
        Subject: C = ID, ST = JKT, L = JKT, O = RH, OU = GPS, CN = edwin.baculsoft.com, emailAddress = edwin@redhat.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
.....

once we generated our self-signed certificate, we can deploy them to Openshift by using below command,

$ oc create secret tls tls-secret --cert=localhost.crt \
	--key=localhost.key -n openshift-ingress

And patch our ingress operator to use our newly created secret,

$ oc patch ingresscontroller.operator default \
	--type=merge -p '{"spec":{"defaultCertificate": {"name": "tls-secret"}}}' \
	-n openshift-ingress-operator

We can validate whether our IngressController is reading our custom certificate by using below command,

$ oc get ingresscontroller default -oyaml

apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
  creationTimestamp: "2023-06-20T05:04:35Z"
  finalizers:
  - ingresscontroller.operator.openshift.io/finalizer-ingresscontroller
  generation: 2
  name: default
  namespace: openshift-ingress-operator
  resourceVersion: "1025274"
  uid: ab6a3f51-cc40-4d85-a988-568eb5358bc5
spec:
  clientTLS:
    clientCA:
      name: ""
    clientCertificatePolicy: ""
  defaultCertificate:
    name: tls-secret

And validate it by using CURL command,

$ curl -kv https://console-openshift-console.my-openshift.com/
*   Trying [::1]:443...
* Connected to console-openshift-console.my-openshift.com (::1) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: C=ID; ST=JKT; L=JKT; O=RH; OU=GPS; CN=edwin.baculsoft.com; emailAddress=edwin@redhat.com
*  start date: Oct 16 05:52:53 2023 GMT
*  expire date: Feb 27 05:52:53 2025 GMT
*  issuer: C=ID; ST=Jakarta; L=Jakarta; O=RH; OU=GPS; CN=localhost; emailAddress=edwin@redhat.com
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.x
> GET / HTTP/1.1
> Host: console-openshift-console.my-openshift.com
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< referrer-policy: strict-origin-when-cross-origin
< set-cookie: csrf-token=xxxxx
< x-content-type-options: nosniff

Using Secret Credential to Connect to Gitlab in Jenkins

Jenkins is a famous CICD tools that can orchestrate our build and deployment strategy, which can also connect with other CICD toolings such as sourcecode management, or security scanning tools.

But sometimes access to those toolings are limited therefore we need to provide some credentials, but dont want those credentials to be displayed in a plain text. This is where Jenkins Credentials fits into the picture.

We can leverage Jenkins Credentials to store credentials as a secret which can be call by our pipeline directly,

We can start by creating a “Username with password” and put our Gitlab username and password there, dont forget to set the ID for this credentials which is going to be called later from our pipeline.

We can call the saved credentials from pipeline by using “withCredentials” mechanism

node() {
    stage ('git clone') {
        sh "git config --global http.sslVerify false"
        withCredentials([usernamePassword(credentialsId: 'my-gitlab-credential', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
            sh "git clone https://\${USERNAME}:\${PASSWORD}@gitlab.company.com/app/my-repo.git source "
        }
    }
}

A successful pipeline would generate below logs,

Started by user developer
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/jobs/my-pipeline/workspace
[Pipeline] {
[Pipeline] stage
[Pipeline] { (git clone)
[Pipeline] sh
+ git config --global http.sslVerify false
[Pipeline] withCredentials
Masking supported pattern matches of $USERNAME or $PASSWORD
[Pipeline] {
[Pipeline] sh
+ git clone https://****:****@gitlab.company.com/app/my-repo.git source
Cloning into 'source'...
[Pipeline] }
[Pipeline] // withCredentials
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

And we can see from above logs that our pipeline is successfully executed.