Test ORDS locally with Testcontainers, Oracle AI Database Free, and MongoDB

Want to run Oracle REST Data Services (ORDS) for integration tests, local experiments, or prototyping? This sample combines ORDS and Oracle AI Database Free in a disposable test environment with Testcontainers and one Maven command.

If you want to skip straight to the code, the full sample is here: ords-testcontainers sample on GitHub

The module exercises three useful things in one place:

  1. Start Oracle AI Database Free in a JUnit test with Testcontainers.
  2. Start ORDS in a second container on the same Docker network and enable schemas during startup.
  3. Verify both a normal ORDS HTTP endpoint and MongoDB CRUD over ORDS’s MongoDB API.
Diagram illustrating the local testing setup for Oracle REST Data Services (ORDS) using a shared Docker network, featuring components like JUnit Test, ORDS Container, MongoDB, and Oracle AI Database Free.

Prerequisites

You only need a few things:

  • Java 21+
  • Maven
  • A Docker-compatible container runtime
  • Access to container-registry.oracle.com/database/ords:latest

Run it locally

From the module root, run:

mvn test

The test verifies ORDS startup, the ORDS Database Admin API, and MongoDB CRUD via ORDS.

The source for this module is here: ords-testcontainers sample on GitHub

How the sample works

An Oracle AI Database Free container is initialized with the following users:

  • ordsuser for the ORDS Database API example
  • mongouser for the MongoDB CRUD example

Then an ORDS container starts, points it at the database container, and REST-enables both schemas.

That the test then verifies:

  • ORDS over HTTP on port 8080
  • ORDS’s MongoDB API on port 27017

The custom ORDS container

The key piece is the OrdsContainer class. This class extends Testcontainer’s GenericContainer to bundle ORDS as a reusable container type:

public class OrdsContainer extends GenericContainer<OrdsContainer> {
    public static final String DEFAULT_IMAGE = "container-registry.oracle.com/database/ords:latest";
    public static final int HTTP_PORT = 8080;
    public static final int HTTPS_PORT = 8443;
    public static final int MONGODB_API_PORT = 27017;

    public OrdsContainer withDatabaseConnectionString(String connectionString) {
        return withEnv("CONN_STRING", connectionString);
    }

    public OrdsContainer withOraclePassword(String oraclePassword) {
        return withEnv("ORACLE_PWD", oraclePassword);
    }

    public OrdsContainer withSchema(String username, String password, String connectDescriptor) {
        schemas.add(new SchemaConfiguration(username, password, connectDescriptor));
        return self();
    }
}

// ... class methods

On startup, the class waits for ORDS over HTTP and runs ORDS.ENABLE_SCHEMA for each configured schema.

See the full container class: src/main/java/com/example/ords/OrdsContainer.java

Combining Oracle AI Database Free and ORDS containers as a test component

The test uses a shared Testcontainers network so ORDS can reach Oracle AI Database Free by a container alias:

private static final Network NETWORK = Network.newNetwork();

private static final OracleContainer oracleContainer = new OracleContainer(DATABASE_IMAGE)
        .withStartupTimeout(Duration.ofMinutes(5))
        .withPassword(ADMIN_PASSWORD)
        .withNetwork(NETWORK)
        .withNetworkAliases(DATABASE_ALIAS);

private static final OrdsContainer ordsContainer = new OrdsContainer()
        .withNetwork(NETWORK)
        .withDatabaseConnectionString(DATABASE_CONNECTION)
        .withOraclePassword(ADMIN_PASSWORD)
        .withSchema(DB_API_ADMIN_USERNAME, DB_API_ADMIN_PASSWORD, SCHEMA_CONNECTION)
        .withSchema(MONGO_USERNAME, MONGO_PASSWORD, SCHEMA_CONNECTION);

@BeforeAll
static void startContainers() throws IOException, InterruptedException {
    oracleContainer.start();
    initializeDatabase();
    ordsContainer.start();
}

// ...test methods

That startup sequence works like this:

  1. Start Oracle AI Database Free.
  2. Run a SQL initialization script.
  3. Start ORDS against that database.

See the full test here: src/test/java/com/example/ords/OrdsContainerIntegrationTest.java

Database Admin API and MongoDB users

The SQL setup script creates users for the ORDS APIs; a Database Admin API user, and a MongoDB API user:

ALTER SESSION SET CONTAINER = freepdb1;

CREATE USER ordsuser IDENTIFIED BY ordsuserpwd QUOTA UNLIMITED ON users;
GRANT connect, pdb_dba TO ordsuser;

CREATE USER mongouser IDENTIFIED BY mongouserpwd QUOTA UNLIMITED ON users;
GRANT create session, create table, soda_app TO mongouser;

The ordsuser account is used to test the ORDS Database Admin API. The mongouser account is used to test CRUD operations through the MongoDB translation API.

See the init script: src/test/resources/ords_init.sql

Verifying the ORDS Database API

We run a simple HTTP request to test the ORDS Database Admin API:

HttpResponse<String> response = HTTP_CLIENT.send(
        HttpRequest.newBuilder(URI.create(ordsDatabaseApiUrl("database/version")))
                .header("Authorization", basicAuth(DB_API_ADMIN_USERNAME, DB_API_ADMIN_PASSWORD))
                .GET()
                .timeout(Duration.ofSeconds(30))
                .build(),
        HttpResponse.BodyHandlers.ofString()
);

assertEquals(200, response.statusCode());

This is would be awkward to test with mocks, but straightforward with disposable containers. You get a real ORDS instance, a real HTTP response, and a real JSON payload back from the database admin API.

The MongoDB part is the fun part

The most interesting part of the test sample is that it uses the MongoDB Java driver to talk to ORDS:

ConnectionString connectionString = new ConnectionString(
        "mongodb://%s:%s@%s:%d/%s?authMechanism=PLAIN&authSource=%%24external&tls=true&retryWrites=false&loadBalanced=true"
                .formatted(
                        MONGO_USERNAME,
                        MONGO_PASSWORD,
                        ordsContainer.getHost(),
                        ordsContainer.getMongoDbApiPort(),
                        MONGO_USERNAME
                )
);

return MongoClientSettings.builder()
        .applyConnectionString(connectionString)
        .applyToSslSettings(builder -> builder
                .enabled(true)
                .invalidHostNameAllowed(true)
                .context(INSECURE_TLS_CONTEXT))
        .build();

That’s standard mongodb-driver-sync code, nothing Oracle-specific. The test then does a straightforward CRUD cycle:

collection.insertOne(originalDocument);

Document insertedDocument = collection.find(Filters.eq("_id", documentId)).first();

collection.updateOne(
        Filters.eq("_id", documentId),
        Updates.set("credits", 15)
);

collection.deleteOne(Filters.eq("_id", documentId));

If you’ve used the MongoDB Java driver, this should look familiar: ORDS provides a translation layer for MongoDB HTTP calls while your application code can use the same MongoDB clients.

See the full MongoDB setup and CRUD test: src/test/java/com/example/ords/OrdsContainerIntegrationTest.java

Note: the sample trusts the ORDS certificate for convenience in a local test. This is a test-only shortcut. Usually, you’d supply ORDS with a trusted certificate and configure the client accordingly.

Why this is a good local developer workflow

This sample keeps the barrier to entry low:

  • run everything locally, using free-tier versions
  • the stack is disposable
  • the code stays close to plain JUnit and Testcontainers
  • the ORDS example is not limited to simple HTTP checks
  • the MongoDB example shows a capability that many developers will immediately understand

This is a great resource for devs evaluating ORDS, testing ORDS-backed application code, or exploring Oracle AI Database Free.

Next steps

If you want to keep going after this sample, the best next move is to explore the source directly. This sample can be adapted to work with Testcontainers in other languages, like Typescript, Python, or Go. If you attempt this, let me know!

Leave a Reply

Discover more from andersswanson.dev

Subscribe now to keep reading and get access to the full archive.

Continue reading