| name | bx-orm-testing |
| description | Use when writing or reviewing ORM tests: BaseORMTest patterns, JUnit 5 setup, Docker-compose MySQL integration, seed.sql data, BoxLang runtime initialization in tests, entity model test fixtures (Vehicle, Manufacturer, Feature, AlternateDS), BIF testing (entityLoad, entitySave, entityNew, ormFlush), transaction testing, and ORM configuration for test environments. |
| version | 1.0.0 |
| domain | bx-orm |
| triggers | orm test, ORM testing, BaseORMTest, entity test, BIF test, integration test, MySQL test, docker-compose test, seed data, test fixture, orm test pattern, TransactionManagerTest, entity load test, entity save test |
| role | expert |
| scope | bx-orm |
| related-skills | junit-expert, mockito-expert, testcontainers-expert, bx-orm-bif-development |
BoxLang ORM — Testing
Overview
ORM testing requires a running database, a configured BoxLang runtime with ORM enabled, and entity model fixtures. The test infrastructure uses Docker Compose for MySQL, JUnit 5 for test execution, and BaseORMTest as the shared test superclass.
Test Infrastructure
flowchart TB
subgraph "Pre-Test"
A["docker-compose up -d<br/>(MySQL container)"]
B["seed.sql<br/>(schema + data)"]
end
subgraph "Test Execution"
C["BaseORMTest<br/>@BeforeEach setup"]
C --> D["Start BoxLang runtime"]
C --> E["Execute Application.bx<br/>(ORM config)"]
D --> F["Test method"]
end
subgraph "Post-Test"
G["@AfterEach teardown"]
G --> H["Shutdown BoxLang runtime"]
end
Key Files
| File | Location | Purpose |
|---|
BaseORMTest.java | src/test/java/tools/ | Shared test superclass; starts/stops BoxLang runtime |
JDBCTestUtils.java | src/test/java/tools/ | JDBC helper utilities for tests |
docker-compose.yml | / (project root) | MySQL 8 container definition |
seed.sql | src/test/resources/ | Schema DDL + seed data |
Application.bx | src/test/resources/app/ | Test ORM configuration |
BaseORMTest
The foundation for all ORM integration tests:
package tools;
import ortus.boxlang.runtime.BoxRuntime;
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.context.RequestBoxContext;
import ortus.boxlang.runtime.scopes.VariablesScope;
import ortus.boxlang.runtime.types.IStruct;
public abstract class BaseORMTest {
protected static BoxRuntime instance;
protected IBoxContext context;
protected VariablesScope variables;
@BeforeEach
public void setUp() {
instance = BoxRuntime.getInstance();
context = new RequestBoxContext();
instance.executeSource(
Files.readString( Path.of( "src/test/resources/app/Application.bx" ) ),
context
);
variables = context.getVariablesScope();
}
@AfterEach
public void tearDown() {
if ( context != null ) {
instance.executeSource( "ormCloseAllSessions()", context );
}
}
}
Writing an ORM Test
BIF Test Example
package ortus.boxlang.modules.orm.bifs;
import static com.google.common.truth.Truth.assertThat;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import ortus.boxlang.runtime.runnables.IClassRunnable;
import ortus.boxlang.runtime.types.Array;
import tools.BaseORMTest;
public class EntityLoadTest extends BaseORMTest {
@DisplayName( "Can load all entities of a type" )
@Test
public void testLoadAll() {
Array results = (Array) instance.executeSource(
"""
entityLoad( "Vehicle" )
""",
context
);
assertThat( results.size() ).isGreaterThan( 0 );
}
@DisplayName( "Can load entity by primary key" )
@Test
public void testLoadByPK() {
IClassRunnable entity = (IClassRunnable) instance.executeSource(
"""
entityLoadByPK( "Vehicle", 1 )
""",
context
);
assertThat( entity ).isNotNull();
assertThat( entity.getVariablesScope().get( "make" ) ).isNotNull();
}
@DisplayName( "Can load with filter criteria" )
@Test
public void testLoadWithFilter() {
Array results = (Array) instance.executeSource(
"""
entityLoad( "Vehicle", { make : "Toyota" }, "model" )
""",
context
);
assertThat( results.size() ).isGreaterThan( 0 );
}
}
Entity CRUD Test Example
@DisplayName( "Can create, read, update, and delete an entity" )
@Test
public void testEntityCRUD() {
instance.executeSource(
"""
vehicle = entityNew( "Vehicle", {
make : "Honda",
model : "Civic",
year : 2024
} )
entitySave( vehicle )
ormFlush()
""",
context
);
IClassRunnable vehicle = (IClassRunnable) variables.get( "vehicle" );
assertThat( vehicle.getVariablesScope().get( "make" ) ).isEqualTo( "Honda" );
instance.executeSource(
"""
vehicle.model = "Accord"
entitySave( vehicle )
ormFlush()
""",
context
);
assertThat( vehicle.getVariablesScope().get( "model" ) ).isEqualTo( "Accord" );
instance.executeSource(
"""
entityDelete( vehicle )
ormFlush()
""",
context
);
}
Transaction Test Example
@DisplayName( "transaction block commits on success" )
@Test
public void testTransactionCommit() {
instance.executeSource(
"""
transaction {
entitySave( entityNew( "Manufacturer", {
name : "Tesla Inc",
address : "1 Tesla Road"
} ) )
}
result = entityLoad( "Manufacturer", { name : "Tesla Inc" }, true )
""",
context
);
assertThat( variables.get( "result" ) ).isNotNull();
}
Test Entity Models
Test entities are BoxLang persistent classes in src/test/resources/app/models/:
src/test/resources/app/models/
├── Vehicle.bx # Main test entity (make, model, year)
├── Manufacturer.bx # Related entity (name, address)
├── Feature.bx # Feature flags or options
├── AlternateDS.cfc # Tests alternate datasource binding
├── EventHandler.bx # Tests entity event handling
├── Unresolvable.bx # Tests error handling
└── CustomNamingStrategy.bx
Sample Test Entity
component
persistent = "true"
table = "vehicles"
entityname = "Vehicle"
{
property name="id" fieldtype="id" generator="increment";
property name="make" type="string" length="100";
property name="model" type="string" length="100";
property name="year" type="numeric";
}
Database Setup
docker-compose.yml
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: bxorm_test
MYSQL_USER: boxlang
MYSQL_PASSWORD: boxlang
ports:
- "3306:3306"
volumes:
- ./src/test/resources/seed.sql:/docker-entrypoint-initdb.d/seed.sql
seed.sql
The seed file creates tables and optional test data:
CREATE TABLE IF NOT EXISTS vehicles (
id INT AUTO_INCREMENT PRIMARY KEY,
make VARCHAR(100) NOT NULL,
model VARCHAR(100) NOT NULL,
year INT
);
CREATE TABLE IF NOT EXISTS manufacturers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
address VARCHAR(500)
);
INSERT INTO vehicles (make, model, year) VALUES
('Toyota', 'Camry', 2023),
('Honda', 'Civic', 2022),
('Ford', 'Mustang', 2024);
Test Application Configuration
Application.bx
class {
this.name = "ORMTestApp"
this.datasources = {
"bxorm_test" : {
driver : "mysql",
host : "localhost",
port : 3306,
database : "bxorm_test",
username : "boxlang",
password : "boxlang"
}
}
this.ormSettings = {
datasource : "bxorm_test",
entityPaths : [ "#this.mapping#/models" ],
generateMappings : true,
ddl : "update",
logSQL : false,
namingStrategy : "MacroCaseNamingStrategy"
}
}
Test Organization
src/test/java/ortus/boxlang/modules/orm/
├── TransactionManagerTest.java # Transaction lifecycle tests
├── bifs/ # BIF-specific tests
├── config/ # Configuration tests
└── mapping/ # Entity mapping tests
Best Practices
- Always extend
BaseORMTest — it handles BoxLang runtime startup/shutdown; never manage the runtime manually in tests.
- Use Google Truth for assertions — the project uses
com.google.common.truth.Truth.assertThat() for fluent assertions.
- Use
@DisplayName annotations — JUnit 5 display names make test reports readable.
- Clean up after each test —
ormFlush() or ormClearSession() between tests to prevent state leakage.
- Use
// @formatter:off / @formatter:on — these markers prevent Spotless/Eclipse formatter from mangling multi-line BoxLang source strings in Java.
- Test with realistic data — seed meaningful data that exercises real-world entity relationships (Vehicles belong to Manufacturers, etc.).
- Parallel test execution is supported — tests use separate contexts; ensure database isolation when running in parallel.
- Docker Compose must be running — run
docker-compose up -d before executing integration tests; the seed.sql auto-initializes the schema.