IBM DevOps Loop introduces Code Genie, a built-in, general-purpose feature for background PR automation. Triggered by pull requests (PRs), it automatically launches a dev container to clone your repository, execute custom commands, collect JSON results, and posts comments directly to the PR to approve or reject the merge.
The core components interact as follows:
-
DevOps Loop: A suite of capabilities managing every stage of the software development lifecycle in a continuous loop.
-
Control Repository: A source control solution providing Git repositories to manage code. Code Genie runs here, triggering automatically on PR creation to execute your custom logic.
This tutorial provides a step-by-step walkthrough to configure Code Genie for JUnit testing, establishing an automatic check that tests your changes before they can be merged into your main branch during pull requests.
Running JUnit Tests with Code Genie
As a developer, you finish writing code, create a pull request (PR), and wait for a review. Checking every PR is slow and repetitive. When deadlines are tight, it is easy to skip these tests entirely. Code Genie removes this manual step by running your checks in the background as soon as a PR is created in Control. It clones the repository into a dev container, runs the commands you define, captures the results in JSON, and updates the PR with an approval or rejection based on the outcome.
A Flexible Assistant Beyond Unit Testing
While this walkthrough focuses on running JUnit tests, it is helpful to note that Code Genie can handle various tasks. You can configure it to handle AI-driven code reviews, linting, or automatic approvals for documentation updates. For a comprehensive list of what the automation assistant can do, refer to the Code Genie Documentation.
In this blog, we focus on how Code Genie can help you automatically verify the quality of a Java application by running JUnit tests.
Technical Prerequisites
Before you begin, ensure that you have:
JUnit Test Scenario
To demonstrate Code Genie in action, we use a simple sample application called BankApp, which handles deposits, withdrawals, and transaction tracking.
The backend is written in Java, and we will use JUnit with Code Genie to test this code. The user interface is built with Java Swing; while you could use Code Genie with other testing frameworks to validate the UI code, that is currently out of scope for this post.

Our Code and the Feature Branch
Our codebase and project files will reside within the Git repository management system of Control.
The development and verification process for this use case is as follows:
-
Active Development on the Feature Branch: You commit and push your banking project files directly into a dedicated test development branch.
-
The Pull Request Trigger: The automation sequence is triggered only when you create a pull request to merge your project changes from the feature branch into your protected production branch. This trigger is tied to webhook configuration, which we will cover in detail later in this post.
-
The Automation Assistant Execution: The moment the PR event is registered by Control, the automation assistant initializes automatically. It checks out the exact code state from your feature branch, spins up your optimized, headless dev container, and executes your test suite.
The Project Folder Structure
Inside our repository, the files are organized in a standard Maven format:

The pom.xml at the root of BankApp is Maven’s build configuration file. It defines two key dependencies: JUnit Jupiter 5.10.2, used for the test suite, and Maven Surefire Plugin 3.2.5, which runs tests during mvn clean test and generates XML reports in target/surefire-reports/ for later processing by run-junit.sh, which is discussed later in the blog.
Note: The Java Code (Account.java), testing code (AccountTest.java) and pom.xml - all are available in the public github repository BankApp. The testing code contains 6 unit tests.
Verifying the Test Suite Locally
Before handing the reins over to cloud automation, it is best practice to verify that the test suite runs flawlessly in a local development environment.
When executing the test suite locally inside VS Code using the command line, the tests pass successfully:
mvn clean test

Why this matters: This step confirms that the baseline Java code passes all 6 tests. We include this checkpoint to establish expectations: when the code is submitted via a PR, Code Genie should detect and process these exact same 6 passing tests inside Control.
Repository Setup and Code Genie Environment Configuration
The following steps will guide you through mapping the repository layout, building a lightweight environment, and configuring your webhook triggers to get Code Genie up and running.
Step 1: The Repository Layout
When you look at your teamspace dashboard on Control, you will notice that there is a specific layout to keep source code and configuration json data separate:

-
BankApp (the Control repository inherits the name of the loop you created): A configuration file named .devops-loop/code-config.jsonc, which tells Code Genie what to do, resides here.
-
.devopsconfig: A configuration repository managed by Control for your teamspace containing environment-specific folders housing their specific devcontainer.json. You can learn more about dev container and devcontainer.json file in the documentation.
Step 2: Preparing the Code Genie Environment
To keep things fast, Code Genie doesn’t create a new test environment for every PR. Instead, you build and push a public Docker image once, and the automation assistant reuses it for subsequent runs.
Creating the Environment
We create a Dockerfile that builds a secure, Java-ready container on a Red Hat UBI 9 base. Running initially as root, it installs Java 17, Maven, and required tooling, sets up encrypted Git authentication using pass and Git Credential Manager, adds code-server for a browser-based IDE, creates the required directories, and configures enterprise certificate permissions. Once setup is complete, it injects the run-junit.sh JUnit runner script and permanently drops to a non-root user (codeuser, UID 1001), a requirement for Code Genie to run safely inside the container.
📘 How the Helper Script Works: The “run-junit.sh” script is embedded into the Docker image using the COPY instruction. When executed, it runs mvn clean test, captures the terminal output, parses the Surefire XML reports to summarize outcomes, and applies the --reject flag to control PR blocking. It then generates a structured JSON report for Code Genie and automatically posts any failures as a clean Markdown table directly onto the PR timeline.
Building the Image and Creating the Dev Container
Once your Dockerfile and run-junit.sh are ready, build and push the image from your local terminal:
devcontainer build --workspace-folder . --image-name sarasonia/junit-container:1.1
docker push sarasonia/junit-container:1.1
Note: You can learn more about devcontainers CLI here. It is basically a terminal tool for building and running dev containers from devcontainer.json. You can also refer to the product documentation page.
The Docker image sarasonia/junit-container:1.1 is available at Docker Hub.
Now, commit and push your devcontainer.json file with the correct image key inside the .devopsconfig repository under devcontainers/devcontainer-junit/.devcontainer/devcontainer.json:

For more details on creating dev containers, see the DevOps Loop: Configuring Dev Containers Documentation.
Optimizing with "Non-IDE" Containers
In devcontainer.json, we set "ide": "false". If omitted (or set to "true"), the container includes a browser-accessible IDE. Since Code Genie runs entirely in the background, it doesn’t need one. Removing it enables faster startup and lower CPU/memory usage. For more information, refer to the DevOps Loop: Non-IDE Dev Container Documentation.
Step 3: Setting Up the Webhook Listener
The webhook is what wakes Code Genie up — it listens for PR events and triggers the testing workflow automatically whenever a PR is opened or updated. If your repository was created as part of loop creation, the webhook is set up for you. If you created the repository manually, you will need to configure it yourself.
See Creating Webhooks for details.
Step 4: Writing code-config.jsonc for JUnit Testing
Open the .devops-loop/code-config.jsonc file in your repository. This file tells Code Genie which container to use and what command(s) to run on every PR.
The commands must map directly to what run-junit.sh expects:
-
${code-folder}: The working project directory where Maven executes.
-
${result-json}: The target path where the script writes its execution summary in JSON format for Code Genie to parse.
-
--reject=ALWAYS: Instructs Code Genie to hard-block the PR if a single test fails.

Step 5: Code Genie in Action: Reviewing Your PR
When your code changes are ready, open a Pull Request in Control to merge from the feature branch into the main branch.

The webhook instantly notifies Code Genie. It starts up the lightweight non-IDE container, targets your feature branch code, executes the testing script, and posts a cleanly formatted report directly onto your PR timeline.
Depending on your code quality, there are two possible outcomes:
Case 1: Tests Passing Successfully
The automation runs successfully and captures the execution flow, matching the local test results exactly. Because all indicators are green, Code Genie automatically updates the PR review status to "approved these changes", indicating that it is ready to be merged.
Case 2: Tests Failing
If a bug is introduced into the feature branch that breaks a test, Code Genie acts as a safeguard. It will reject the PR instantly, keeping your main branch functional and secure while instructing you to make the necessary fixes in your feature branch. Failed test results are neatly detailed in a Markdown table directly on the timeline:

Conclusion
The Code Genie feature in DevOps Loop automates pull request testing, providing a reliable safeguard for production code. Its PR pipeline is driven by three components: a webhook that wakes Code Genie up, code-config.jsonc that defines what to run, and the helper script (run-junit.sh) that executes the tests and produces the results.
JUnit testing is just one use case - explore the Code Genie documentation for more possibilities.