In this article, we’ll walk through how to configure an Oracle Database Free container for go application tests using testcontainers-go.
What is Testcontainers?
Testcontainers is a popular framework that lets you test applications against real, disposable containers. It helps ensure your tests run against realistic environments without requiring complex infrastructure setup.
Testcontainers for Oracle Database (go)
The testcontainers-go module makes it simple to create and extend containers. Let’s write an implementation, oracle_container.go, that starts a database container with a configurable username and password:
package testcontainers
import (
"context"
"database/sql"
"fmt"
"github.com/anders-swanson/oracle-database-java-samples/golang/connection"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
const (
containerPort = "1521/tcp"
containerLogReady = "DATABASE IS READY TO USE!"
appUserEnvVar = "APP_USER"
appUserPasswordEnvVar = "APP_USER_PASSWORD"
oracleRandomPasswordEnvVar = "ORACLE_RANDOM_PASSWORD"
)
type OracleContainer struct {
username string
password string
*testcontainers.DockerContainer
}
func NewOracleContainer(ctx context.Context, image, appUser, appUserPassword string, opts ...testcontainers.ContainerCustomizer) (*OracleContainer, error) {
// Configure Oracle Container with default options
opts = append(opts, testcontainers.WithExposedPorts(containerPort),
testcontainers.WithWaitStrategy(
wait.ForListeningPort(containerPort),
wait.ForLog(containerLogReady),
),
testcontainers.WithEnv(map[string]string{
oracleRandomPasswordEnvVar: "y",
appUserEnvVar: appUser,
appUserPasswordEnvVar: appUserPassword,
}),
)
// Start the container
container, err := testcontainers.Run(ctx,
image,
opts...,
)
if err != nil {
return nil, err
}
return &OracleContainer{
username: appUser,
password: appUserPassword,
DockerContainer: container,
}, nil
}
func (o *OracleContainer) GetDB(ctx context.Context) (*sql.DB, error) {
// Either set DYLD_LIBRARY_PATH to point to the Oracle client libraries,
// or have the client libraries present on your system PATH.
endpoint, err := o.Endpoint(ctx, "")
if err != nil {
return nil, err
}
return connection.NewDatabase(
o.username,
o.password,
fmt.Sprintf("%s/freepdb1", endpoint),
), nil
}We also add a GetDB method to return a Go sql.db connected to the container. This method uses my NewDatabase function to create a basic connection pool, though you may configure the container connection pool as needed.
package connection
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"time"
"github.com/godror/godror"
)
func DefaultLocalhostConnection() *sql.DB {
return NewDatabase("testuser", "testpwd", "localhost:1521/freepdb1")
}
func NewDatabase(username, password, url string) *sql.DB {
// Either set DYLD_LIBRARY_PATH to point to the Oracle client libraries,
// or have the client libraries present on your system PATH.
ldLibraryPath := os.Getenv("DYLD_LIBRARY_PATH")
if ldLibraryPath == "" {
log.Fatalf("DYLD_LIBRARY_PATH unset, cannot find Oracle client libraries")
}
var P godror.ConnectionParams
// If password is not specified, externalAuth will be true, and we'll ignore user input
isExternalAuth := password == ""
externalAuth := sql.NullBool{
Bool: isExternalAuth,
Valid: true,
}
if isExternalAuth {
username = ""
}
// Setup connection parameters
P.Username = username
P.Password = godror.NewPassword(password)
P.ConnectString = url
P.ExternalAuth = externalAuth
// Connection pooling parameters
P.PoolParams.WaitTimeout = time.Second * 5
// Recommended to use minimal pooling,
// otherwise you will manage a single connection
P.PoolParams.SessionIncrement = 1
P.PoolParams.MinSessions = 1
P.PoolParams.MaxSessions = 15
// See P.PoolParams for additional pooling configuration
// Database TNS Admin
tnsAdmin := os.Getenv("TNS_ADMIN")
if tnsAdmin != "" {
P.ConfigDir = tnsAdmin
}
// Optionally set database role
// P.AdminRole = dsn.SysDBA
// Create new database using connection parameters
db := sql.OpenDB(godror.NewConnector(P))
db.SetMaxOpenConns(15)
db.SetMaxIdleConns(15)
db.SetConnMaxLifetime(0)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if _, err := db.ExecContext(ctx, `
begin
dbms_application_info.set_client_info('oracledb_exporter');
end;`); err != nil {
fmt.Println("Could not set CLIENT_INFO.")
}
return db
}Write a test for OracleContainer
We create a simple test that starts an Oracle Database Free container, gets a database connection, and queries the database version.
package testcontainers
import (
"context"
"fmt"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestOracleContainer(t *testing.T) {
// Start an Oracle Database container
ctx := context.Background()
container, err := NewOracleContainer(ctx,
"gvenzl/oracle-free:23.26.0-slim-faststart",
"testuser",
"testpwd",
)
assert.Nil(t, err)
// Get a database connection for the container
db, err := container.GetDB(ctx)
assert.Nil(t, err)
// Query the version from the database
queryCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var banner string
err = db.QueryRowContext(queryCtx, "SELECT banner FROM v$version WHERE ROWNUM = 1").Scan(&banner)
assert.Nil(t, err)
assert.Equal(t, "Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free", banner)
fmt.Println(banner)
}Run the test
To run the test, set the DYLD_LIBRARY_PATH environment variable so it points to the directory containing your Oracle Instant Client. If you don’t have an Oracle Instant Client library downloaded, get it Here.
The instantclient library is required because godror is a thick database client.
export DYLD_LIBRARY_PATH=/path/to/instantclientFrom the golang directory, run the test using go test:
go test ./testcontainersThat’s it! The test will run, start the container, and then automatically clean up the database on completion.

Leave a Reply