Automated integration testing is a critical part of any mature delivery pipeline. In GitOps-driven environments—especially those based on Argo CD—the challenge is not whether to run tests, but how to run them reliably, repeatably, and declaratively as part of the deployment lifecycle.
In this post, we’ll walk through a practical approach to automatically executing SoapUI tests as an Argo CD PostSync hook, using:
- A custom Docker image built on top of the official SoapUI TestRunner image
- Maven-based SoapUI projects published to Artifactory
- A custom entrypoint that downloads, executes, reports, and notifies
- Native Kubernetes Jobs triggered by Argo CD hooks
The result is a clean GitOps-compatible solution where tests are versioned, traceable, and tightly coupled to the deployed application.
Why Not Use the Official SoapUI Image Directly?
SmartBear provides an official soapuios-testrunner Docker image, which is great for basic execution. However, in real-world CI/CD and GitOps scenarios, it quickly shows limitations:
- No built-in mail notification
- No easy way to download test artifacts dynamically
- EntryPoint logic exits the container early, making composition difficult
- Limited extensibility for enterprise environments
To address these gaps, I built a thin extension image that preserves the official behavior but adds:
- Artifactory-based test retrieval
- Email notifications with test summaries and attachments
- Better control over exit codes
High-Level Architecture
Here’s the flow at a high level:
- Developers create SoapUI test suites in a Maven module next to the application
- The test module is packaged as a JAR and pushed to Artifactory
- Argo CD deploys the application
- A PostSync hook Job runs the custom SoapUI TestRunner image
- The container:
- Downloads the SoapUI project from Artifactory
- Executes the tests
- Collects reports
- Sends email notifications
- Exits with the correct status
For Argo CD even fails are considered a successful deployment, since my goal was to not interfere with the deployment itself. This can be changed however so Argo CD handles failures with SoapUI tests as a failed deployment.
Maven-Based SoapUI Test Projects
To make tests first-class citizens of the delivery process, SoapUI projects are treated like any other build artifact.
Key conventions:
- SoapUI tests live in a dedicated Maven module (e.g.
my-app-soapui-tests) - The module packages exactly one SoapUI XML project
- The resulting artifact is published to Artifactory as a JAR
This enables:
- Versioned test suites
- Reproducibility
- Clear coupling between app version and test version
Extending the Official SoapUI Image
Custom Dockerfile
We start from the official image and layer in what we need:
FROM docker.artifactory.ww-intern.de/smartbear/soapuios-testrunner:5.9.0
USER root
# Install required utilities
RUN apt-get update && \
apt-get install -y \
ca-certificates \
openssl \
libssl1.1 \
curl \
zip \
file \
unzip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Copy custom scripts
COPY scripts/* ./
RUN chmod +x ./customEntrypoint.sh && \
chmod +x ./EntryPoint.sh && \
useradd -u 4444 -r -g root -m -d /home/runner -s /sbin/nologin -c "Runner user" runner && \
chmod -R 770 /usr/local/SmartBear/* && \
chgrp -R 0 /usr/local/SmartBear && \
chmod -R g+rwX /usr/local/SmartBear
USER runner
ENTRYPOINT ["./customEntrypoint.sh"]
Key points:
- We retain the official SoapUI runtime
- We install minimal tooling (
curl,zip,unzip) for Artifactory and reporting - We replace the entrypoint with our own orchestrator
Overriding the Original SoapUI EntryPoint
The original SoapUI EntryPoint.sh exits the container internally, which breaks composability.
To fix this, we copy and modify it:
- Remove
exitstatements - Preserve exit codes in environment variables
- Allow it to be called from a parent script
if [ -d "$MOUNTED_PROJECT_DIR" ]; then cp -a $MOUNTED_PROJECT_DIR/. $PROJECT_DIR fi if [ -d "$MOUNTED_EXT_DIR" ]; then cp -a $MOUNTED_EXT_DIR/. $SOAPUI_DIR/bin/ext fi sed -i "s|COMMAND_LINE|$COMMAND_LINE|" ./RunProject.sh sed -i "s|%project%|$PROJECT_DIR|g" ./RunProject.sh sed -i "s|%reports%|$REPORTS_DIR|g" ./RunProject.sh ./RunProject.sh export EXIT_CODE=$?
This allows us to wrap the SoapUI execution in higher-level logic without losing status information.
The Custom Entrypoint: Orchestrating Test Execution
The core of this solution is a custom container entrypoint that wraps the official SoapUI TestRunner logic and adds the functionality required for a GitOps-driven deployment workflow.
Instead of executing SoapUI directly, the container starts with customEntrypoint.sh, which orchestrates the full test lifecycle in a controlled and extensible way.
Responsibilities of the Custom Entrypoint
The script performs four steps:
1. Download the SoapUI Project from Artifactory
SoapUI projects are published as Maven artifacts to Artifactory. The entrypoint resolves the Maven coordinates provided via environment variables (groupId, artifactId, version) into a download URL, retrieves the JAR, and extracts the SoapUI XML project.
To avoid ambiguous executions, the script enforces that exactly one SoapUI XML file is present in the artifact. If this condition is not met, execution fails immediately.
2. Execute Tests Using the Official SoapUI Logic
Instead of re-implementing the test execution logic, the custom entrypoint delegates to a modified copy of SoapUI’s original EntryPoint.sh.
The original script contains hard exit statements that would terminate the container prematurely. These statements are removed so that:
- The SoapUI runner can be invoked as a subprocess
- The exit code can be captured and forwarded correctly
This preserves the official behavior while making it composable.
3. Collect Results and Send Notifications
During execution, the console output is filtered to extract a concise test summary, which is written to a temporary summary file.
After execution:
- All generated reports are bundled into a ZIP archive
- An optional email notification is sent containing:
- The test summary
- The report archive as an attachment
Email delivery is fully optional and controlled via environment variables, allowing the same image to be reused across environments.
4. Propagate the Correct Exit Code
Finally, the custom entrypoint exits with the same exit code as the SoapUI TestRunner.
This is critical for Argo CD integration:
a non-zero exit code causes the PostSync hook to fail, which in turn marks the deployment as unsuccessful.
From Argo CD’s perspective, the Job is a simple Kubernetes resource. All complexity—artifact retrieval, execution, reporting, and notifications—is handled inside the container, making this approach both GitOps-friendly and easy to operate.
Configuration for Helm
The following values.yaml shows all configuration parameters for the Kubernetes resources.
soapui:
# Job / chart naming
name: soapui-tests
# Job behavior
ttlSecondsAfterFinished: 1200
backoffLimit: 0
# Optional override for SoapUI endpoint
# forcedEndpoint: https://api.example.com
# Optional: run only specific suite or case
# testSuite: "My Test Suite"
# testCase: "My Test Case"
# Custom SoapUI properties (-G key=value)
customProperties: []
# - env=dev
# - timeout=30
# Maven artifact configuration
maven:
groupId: "com.example"
artifactId: "soapui-tests"
version: "1.0.0" # defaults to container.image.tag if unset
# Credentials configuration
credentials:
# Artifactory credentials (required)
artifactoryUser: "artifactory-user"
artifactoryToken: "artifactory-token"
# Optional SoapUI credentials
# soapuiUser: "soapui-user"
# soapuiPassword: "soapui-password"
# Optional secret name override for SoapUI credentials
# secretName: soapui-credentials
# Email notification configuration
email:
enabled: false
recipients: []
# - [email protected]
# - [email protected]
# Resource requests & limits
resources:
limits:
memory: "256Mi"
requests:
memory: "64Mi"
cpu: "100m"
# Scheduling options
nodeSelector: {}
# disktype: ssd
tolerations: []
# - key: "dedicated"
# operator: "Equal"
# value: "soapui"
# effect: "NoSchedule"
affinity: {}
Endpoint Configuration Strategies
The SoapUI test execution supports two complementary strategies for configuring service endpoints.
Endpoint via Environment Variable (Recommended)
The preferred approach is to define the service endpoint as a SoapUI project property that references an environment variable (for example via ${#env#SERVICE_ENDPOINT}). This approach keeps the SoapUI project environment-agnostic while still allowing fine-grained control over endpoints per deployment. It works well with authentication flows such as OAuth, since SoapUI retains full control over how endpoints are resolved internally.
Forced Endpoint Override
As an alternative, the setup supports a forced endpoint configuration.
When a forced endpoint is defined:
- All endpoints used by SoapUI are rewritten at runtime
- Every request is routed to the forced endpoint, regardless of how it is defined in the SoapUI project
- No project-level endpoint configuration is required
This is useful for simple, uniform environments where all requests must be directed to a single base URL.
The forced endpoint mechanism also overrides endpoints used internally by SoapUI, including those involved in OAuth token flows. Because of this, it must not be used with OAuth-based authentication, as token acquisition will fail.
Credential Handling
The SoapUI test setup distinguishes clearly between infrastructure credentials and test-level credentials, ensuring both security and flexibility.
Artifactory Credentials
Artifactory credentials are used exclusively to authenticate against Artifactory in order to download the JAR file that contains the SoapUI test project.
SoapUI Test Credentials (soapuiUser / soapuiPassword)
For convenience, the setup provides two optional default credentials:
soapuiUsersoapuiPassword
These map directly to predefined environment variables that can be referenced inside the SoapUI project.
Important: The SoapUI test suite must explicitly reference these environment variables. If they are not defined in the project, these credentials will have no effect.
You may use your own environment variables which can then be set as a customProperty in the values.yaml.
