Testing Your Application
This guide provides comprehensive instructions for testing your Wheels 3.0 application using TestBox 5. Wheels 3.0 now includes TestBox integration as an enabled option, moving beyond the legacy RocketUnit framework. TestBox is already included in your installation through box.json dependency management.
Overview
TestBox 5 is a next-generation testing framework for ColdFusion (CFML) based on BDD (Behavior Driven Development) and TDD (Test Driven Development), providing a clean, obvious syntax for writing tests. It serves as a comprehensive testing engine with multi-format output capabilities and database testing support.
For comprehensive TestBox documentation, refer to the official TestBox documentation.
TestBox Features
BDD style or xUnit style testing
Testing life-cycle methods
MockBox integration for mocking and stubbing
Extensible reporters (JSON, XML, JUnit XML, Text, Console, TAP, HTML)
Asynchronous testing
Multi-suite capabilities
Test skipping and labels
Code coverage via FusionReactor
For a complete list of features, see the TestBox Features documentation.
Project Directory Structure
Based on real Wheels 3.0 projects, your test structure should be organized as follows:
your-app/
├── app/
│ ├── controllers/
│ ├── models/
│ └── views/
├── config/
├── public/
├── tests/
│ ├── _assets/
│ ├── specs/
│ │ ├── controllers/
│ │ │ ├── ExampleControllerSpec.cfc
│ │ │ ├── PostControllerSpec.cfc
│ │ │ └── [Other Controller Tests]
│ │ └── functions/
│ │ └── ExampleSpec.cfc
│ ├── populate.cfm
│ ├── routes.cfm
│ └── runner.cfm
Note: By default, TestBox runs tests located in the tests/specs/
directory, unless configured otherwise.
TestBox Test Runner Configuration
Main Test Runner
Update tests/runner.cfm
:
For detailed information on TestBox runners and configuration options, refer to the TestBox Runners documentation.
<!--- TestBox Test Runner for Wheels 3.0 --->
<cfsetting requestTimeOut="1800">
<cfscript>
testBox = new testbox.system.TestBox(directory="tests.specs")
setTestboxEnvironment()
if (!structKeyExists(url, "format") || url.format eq "html") {
result = testBox.run(
reporter = "testbox.system.reports.JSONReporter"
);
}
else if(url.format eq "json"){
result = testBox.run(
reporter = "testbox.system.reports.JSONReporter"
);
cfcontent(type="application/json");
cfheader(name="Access-Control-Allow-Origin", value="*");
DeJsonResult = DeserializeJSON(result);
if (DeJsonResult.totalFail > 0 || DeJsonResult.totalError > 0) {
cfheader(statustext="Expectation Failed", statuscode=417);
} else {
cfheader(statustext="OK", statuscode=200);
}
// Check if 'only' parameter is provided in the URL
if (structKeyExists(url, "only") && url.only eq "failure,error") {
allBundles = DeJsonResult.bundleStats;
if(DeJsonResult.totalFail > 0 || DeJsonResult.totalError > 0){
// Filter test results
filteredBundles = [];
for (bundle in DeJsonResult.bundleStats) {
if (bundle.totalError > 0 || bundle.totalFail > 0) {
filteredSuites = [];
for (suite in bundle.suiteStats) {
if (suite.totalError > 0 || suite.totalFail > 0) {
filteredSpecs = [];
for (spec in suite.specStats) {
if (spec.status eq "Error" || spec.status eq "Failed") {
arrayAppend(filteredSpecs, spec);
}
}
if (arrayLen(filteredSpecs) > 0) {
suite.specStats = filteredSpecs;
arrayAppend(filteredSuites, suite);
}
}
}
if (arrayLen(filteredSuites) > 0) {
bundle.suiteStats = filteredSuites;
arrayAppend(filteredBundles, bundle);
}
}
}
DeJsonResult.bundleStats = filteredBundles;
// Update the result with filtered data
count = 1;
for(bundle in allBundles){
writeOutput("Bundle: #bundle.name##Chr(13)##Chr(10)#")
writeOutput("CFML Engine: #DeJsonResult.CFMLEngine# #DeJsonResult.CFMLEngineVersion##Chr(13)##Chr(10)#")
writeOutput("Duration: #bundle.totalDuration#ms#Chr(13)##Chr(10)#")
writeOutput("Labels: #ArrayToList(DeJsonResult.labels, ', ')##Chr(13)##Chr(10)#")
writeOutput("╔═══════════════════════════════════════════════════════════╗#Chr(13)##Chr(10)#║ Suites ║ Specs ║ Passed ║ Failed ║ Errored ║ Skipped ║#Chr(13)##Chr(10)#╠═══════════════════════════════════════════════════════════╣#Chr(13)##Chr(10)#║ #NumberFormat(bundle.totalSuites,'999')# ║ #NumberFormat(bundle.totalSpecs,'999')# ║ #NumberFormat(bundle.totalPass,'999')# ║ #NumberFormat(bundle.totalFail,'999')# ║ #NumberFormat(bundle.totalError,'999')# ║ #NumberFormat(bundle.totalSkipped,'999')# ║#Chr(13)##Chr(10)#╚═══════════════════════════════════════════════════════════╝#Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
if(bundle.totalFail > 0 || bundle.totalError > 0){
for(suite in DeJsonResult.bundleStats[count].suiteStats){
writeOutput("Suite with Error or Failure: #suite.name##Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
for(spec in suite.specStats){
writeOutput(" Spec Name: #spec.name##Chr(13)##Chr(10)#")
writeOutput(" Error Message: #spec.failMessage##Chr(13)##Chr(10)#")
writeOutput(" Error Detail: #spec.failDetail##Chr(13)##Chr(10)##Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
}
}
count += 1;
}
writeOutput("#Chr(13)##Chr(10)##Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
}
}else{
for(bundle in DeJsonResult.bundleStats){
writeOutput("Bundle: #bundle.name##Chr(13)##Chr(10)#")
writeOutput("CFML Engine: #DeJsonResult.CFMLEngine# #DeJsonResult.CFMLEngineVersion##Chr(13)##Chr(10)#")
writeOutput("Duration: #bundle.totalDuration#ms#Chr(13)##Chr(10)#")
writeOutput("Labels: #ArrayToList(DeJsonResult.labels, ', ')##Chr(13)##Chr(10)#")
writeOutput("╔═══════════════════════════════════════════════════════════╗#Chr(13)##Chr(10)#║ Suites ║ Specs ║ Passed ║ Failed ║ Errored ║ Skipped ║#Chr(13)##Chr(10)#╠═══════════════════════════════════════════════════════════╣#Chr(13)##Chr(10)#║ #NumberFormat(bundle.totalSuites,'999')# ║ #NumberFormat(bundle.totalSpecs,'999')# ║ #NumberFormat(bundle.totalPass,'999')# ║ #NumberFormat(bundle.totalFail,'999')# ║ #NumberFormat(bundle.totalError,'999')# ║ #NumberFormat(bundle.totalSkipped,'999')# ║#Chr(13)##Chr(10)#╚═══════════════════════════════════════════════════════════╝#Chr(13)##Chr(10)##Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
}
}
}else{
writeOutput(result)
}
}
else if (url.format eq "txt") {
result = testBox.run(
reporter = "testbox.system.reports.TextReporter"
)
cfcontent(type="text/plain");
writeOutput(result)
}
else if(url.format eq "junit"){
result = testBox.run(
reporter = "testbox.system.reports.ANTJUnitReporter"
)
cfcontent(type="text/xml");
writeOutput(result)
}
// reset the original environment
application.wheels = application.$$$wheels
structDelete(application, "$$$wheels")
if(!structKeyExists(url, "format") || url.format eq "html"){
// Use our html template
type = "App";
include "/wheels/tests_testbox/html.cfm";
}
private function setTestboxEnvironment() {
// creating backup for original environment
application.$$$wheels = Duplicate(application.wheels)
// load testbox routes
application.wo.$include(template = "/tests/routes.cfm")
application.wo.$setNamedRoutePositions()
local.AssetPath = "/tests/_assets/"
application.wo.set(rewriteFile = "index.cfm")
application.wo.set(controllerPath = local.AssetPath & "controllers")
application.wo.set(viewPath = local.AssetPath & "views")
application.wo.set(modelPath = local.AssetPath & "models")
application.wo.set(wheelsComponentPath = "/wheels")
/* turn off default validations for testing */
application.wheels.automaticValidations = false
application.wheels.assetQueryString = false
application.wheels.assetPaths = false
/* redirections should always delay when testing */
application.wheels.functions.redirectTo.delay = true
/* turn off transactions by default */
application.wheels.transactionMode = "none"
/* turn off request query caching */
application.wheels.cacheQueriesDuringRequest = false
// CSRF
application.wheels.csrfCookieName = "_wheels_test_authenticity"
application.wheels.csrfCookieEncryptionAlgorithm = "AES"
application.wheels.csrfCookieEncryptionSecretKey = GenerateSecretKey("AES")
application.wheels.csrfCookieEncryptionEncoding = "Base64"
// Setup CSRF token and cookie. The cookie can always be in place, even when the session-based CSRF storage is being
// tested.
dummyController = application.wo.controller("dummy")
csrfToken = dummyController.$generateCookieAuthenticityToken()
cookie[application.wheels.csrfCookieName] = Encrypt(
SerializeJSON({authenticityToken = csrfToken}),
application.wheels.csrfCookieEncryptionSecretKey,
application.wheels.csrfCookieEncryptionAlgorithm,
application.wheels.csrfCookieEncryptionEncoding
)
if(structKeyExists(url, "db") && listFind("mysql,sqlserver,postgres,h2", url.db)){
application.wheels.dataSourceName = "wheelstestdb_" & url.db;
} else if (application.wheels.coreTestDataSourceName eq "|datasourceName|") {
application.wheels.dataSourceName = "wheelstestdb";
} else {
application.wheels.dataSourceName = application.wheels.coreTestDataSourceName;
}
application.testenv.db = application.wo.$dbinfo(datasource = application.wheels.dataSourceName, type = "version")
local.populate = StructKeyExists(url, "populate") ? url.populate : true
if (local.populate) {
include "populate.cfm"
}
}
</cfscript>
Test Data Population
Update tests/populate.cfm
for test database setup:
<cfscript>
// Populate test database with sample data
try {
// Create test users
testUser = model("User").create({
username: "testuser",
email: "[email protected]",
password: "password123",
firstName: "Test",
lastName: "User"
});
// Create test blog posts
testPost = model("Post").create({
title: "Test Blog Post",
content: "This is a test blog post content.",
userId: testUser.id,
published: true
});
// Create test community content
testCommunityPost = model("CommunityPost").create({
title: "Test Community Post",
content: "Community test content",
userId: testUser.id
});
writeOutput("Test data populated successfully.<br>");
} catch (any e) {
writeOutput("Error populating test data: " & e.message & "<br>");
}
</cfscript>
Writing Controller Tests
TestBox 5 test bundles should extend testbox.system.BaseSpec
and use BDD syntax with describe()
, it()
, and expect()
.
For comprehensive information on TestBox BDD syntax and expectations, see the TestBox BDD documentation and TestBox Expectations documentation.
Example Controller Testing
Create tests/specs/controllers/ExampleControllerSpec.cfc
:
component extends="testbox.system.BaseSpec" {
function beforeAll() {
variables.baseUrl = "http://localhost:8080";
variables.home = variables.baseUrl & "/";
}
function run() {
describe("Front Page Functions Tests", () => {
it("should return 200 status code for home page", () => {
cfhttp(url=variables.home, method="GET", result="response");
expect(response.status_code).toBe(200);
expect(response.responseheader.status_code).toBe(200);
});
it("should contain expected home page content", () => {
cfhttp(url=variables.home, method="GET", result="response");
expect(response.filecontent).toInclude("<title>");
expect(response.filecontent).toInclude("html");
});
it("should have proper content type", () => {
cfhttp(url=variables.home, method="GET", result="response");
expect(response.responseheader["Content-Type"]).toInclude("text/html");
});
});
}
}
API Controller Testing
Create tests/specs/controllers/ApiControllerSpec.cfc
:
component extends="testbox.system.BaseSpec" {
function beforeAll() {
variables.baseUrl = "http://localhost:8080";
variables.apiUrl = variables.baseUrl & "/api";
}
function run() {
describe("GET /api/users", () => {
beforeEach(() => {
// Set up API authentication if needed
variables.headers = {
"Content-Type": "application/json",
"Accept": "application/json"
};
});
it("should return JSON response with 200 status", () => {
cfhttp(
url=variables.apiUrl & "/users",
method="GET",
result="response"
) {
cfhttpparam(type="header", name="Content-Type", value="application/json");
cfhttpparam(type="header", name="Accept", value="application/json");
}
expect(response.status_code).toBe(200);
// Parse JSON response
var jsonResponse = deserializeJSON(response.filecontent);
expect(jsonResponse).toBeStruct();
expect(jsonResponse).toHaveKey("data");
});
});
describe("POST /api/users", () => {
it("should create new user with valid data", () => {
var userData = {
username: "apitest_#createUUID()#",
email: "[email protected]",
password: "password123"
};
cfhttp(
url=variables.apiUrl & "/users",
method="POST",
result="response"
) {
cfhttpparam(type="header", name="Content-Type", value="application/json");
cfhttpparam(type="body", value=serializeJSON(userData));
}
expect(response.status_code).toBe(201);
var jsonResponse = deserializeJSON(response.filecontent);
expect(jsonResponse.data.username).toBe(userData.username);
});
});
}
}
Authentication Controller Testing
Create tests/specs/controllers/AuthenticationControllerSpec.cfc
:
component extends="testbox.system.BaseSpec" {
function beforeAll() {
variables.baseUrl = "http://localhost:8080";
variables.authUrl = variables.baseUrl & "/auth";
}
function run() {
describe("Login Flow", () => {
beforeEach(() => {
// Create test user for authentication tests
variables.testUser = {
username: "authtest",
email: "[email protected]",
password: "password123"
};
});
it("should display login page", () => {
cfhttp(url=variables.authUrl & "/login", method="GET", result="response");
expect(response.status_code).toBe(200);
expect(response.filecontent).toInclude("login");
});
it("should authenticate valid user", () => {
cfhttp(
url=variables.authUrl & "/login",
method="POST",
result="response"
) {
cfhttpparam(type="formfield", name="username", value=variables.testUser.username);
cfhttpparam(type="formfield", name="password", value=variables.testUser.password);
cfhttpparam(type="formfield", name="csrf_token", value=session.csrf_token);
}
// Should redirect after successful login
expect(response.status_code).toBe(302);
expect(response.responseheader).toHaveKey("Location");
});
it("should reject invalid credentials", () => {
cfhttp(
url=variables.authUrl & "/login",
method="POST",
result="response"
) {
cfhttpparam(type="formfield", name="username", value="invalid");
cfhttpparam(type="formfield", name="password", value="invalid");
cfhttpparam(type="formfield", name="csrf_token", value=session.csrf_token);
}
expect(response.status_code).toBe(200);
expect(response.filecontent).toInclude("error");
});
});
describe("Logout Flow", () => {
it("should logout user successfully", () => {
cfhttp(url=variables.authUrl & "/logout", method="POST", result="response") {
cfhttpparam(type="formfield", name="csrf_token", value=session.csrf_token);
}
expect(response.status_code).toBe(302);
});
});
}
}
Post Controller Testing
Create tests/specs/controllers/PostControllerSpec.cfc
:
component extends="testbox.system.BaseSpec" {
function beforeAll() {
variables.baseUrl = "http://localhost:8080";
variables.blogUrl = variables.baseUrl & "/blog";
}
function run() {
describe("Blog Index", () => {
it("should display blog posts list", () => {
cfhttp(url=variables.blogUrl, method="GET", result="response");
expect(response.status_code).toBe(200);
expect(response.filecontent).toInclude("blog");
});
});
describe("Individual Blog Post", () => {
it("should display specific blog post", () => {
cfhttp(url=variables.blogUrl & "/1", method="GET", result="response");
// Either 200 (post exists) or 404 (post doesn't exist)
expect([200, 404]).toInclude(response.status_code);
});
});
describe("Blog Post Creation", () => {
it("should create new blog post with valid data", () => {
var postData = {
title: "Test Blog Post",
content: "This is test content for the blog post.",
published: true
};
cfhttp(
url=variables.blogUrl & "/create",
method="POST",
result="response"
) {
cfhttpparam(type="formfield", name="title", value=postData.title);
cfhttpparam(type="formfield", name="content", value=postData.content);
cfhttpparam(type="formfield", name="published", value=postData.published);
cfhttpparam(type="formfield", name="csrf_token", value=session.csrf_token);
}
// Should redirect after successful creation
expect([201, 302]).toInclude(response.status_code);
});
});
}
}
Writing Function Tests
For detailed information on testing functions and utility methods, refer to the TestBox Unit Testing documentation.
Example Function Testing
Create tests/specs/functions/ExampleSpec.cfc
:
component extends="testbox.system.BaseSpec" {
function run() {
describe("String Helper Functions", () => {
it("should strip spaces correctly", () => {
var result = stripSpaces(" hello world ");
expect(result).toBe("helloworld");
});
it("should format currency properly", () => {
var result = formatCurrency(1234.56);
expect(result).toInclude("$");
expect(result).toInclude("1,234.56");
});
});
describe("Date Helper Functions", () => {
it("should format date correctly", () => {
var testDate = createDate(2024, 1, 15);
var result = formatDisplayDate(testDate);
expect(result).toInclude("Jan");
expect(result).toInclude("15");
expect(result).toInclude("2024");
});
});
describe("Validation Functions", () => {
it("should validate email addresses", () => {
expect(isValidEmail("[email protected]")).toBeTrue();
expect(isValidEmail("invalid-email")).toBeFalse();
expect(isValidEmail("")).toBeFalse();
});
it("should validate phone numbers", () => {
expect(isValidPhone("(555) 123-4567")).toBeTrue();
expect(isValidPhone("555-123-4567")).toBeTrue();
expect(isValidPhone("invalid")).toBeFalse();
});
});
}
}
Running Your Tests
Wheels 3.0 Test URL Structure
Wheels 3.0 provides convenient URL routing for both TestBox and legacy testing:
TestBox Testing URLs:
# Run your application TestBox tests
http://localhost:8080/wheels/app/tests
# Run Wheels core framework TestBox tests
http://localhost:8080/wheels/core/tests
Legacy Testing URLs (RocketUnit):
# Run your application legacy tests
http://localhost:8080/wheels/legacy/app/tests
# Run Wheels core framework legacy tests
http://localhost:8080/wheels/legacy/core/tests
Web Interface Access
Access your TestBox tests through multiple formats:
# HTML Interface (default)
http://localhost:8080/wheels/app/tests
# JSON Output (for CI/CD)
http://localhost:8080/wheels/app/tests?format=json
# Plain Text Output
http://localhost:8080/wheels/app/tests?format=txt
For more information on running tests and available formats, see the TestBox Web Runner documentation.
Framework Core Testing
You can also run tests for the Wheels framework itself:
# Run Wheels core TestBox tests
http://localhost:8080/wheels/core/tests
# Run Wheels core legacy tests
http://localhost:8080/wheels/legacy/core/tests
Advanced URL Parameters
Customize your test runs using the convenient URLs:
# Run specific test bundles
http://localhost:8080/wheels/app/tests?bundles=HomeControllerSpec,ApiControllerSpec
# Run tests with specific labels
http://localhost:8080/wheels/app/tests?labels=integration,api
# Exclude certain tests
http://localhost:8080/wheels/app/tests?excludes=slow,external
# Combine parameters
http://localhost:8080/wheels/app/tests?format=json&db=mysql&populate=true&bundles=ApiControllerSpec
Test Coverage Areas
Your test suite should provide comprehensive coverage for:
HTTP Response Testing
Status codes (200, 404, 500, 302, etc.)
Response headers (Content-Type, Location, etc.)
Response content validation
Redirect behavior
Controller Functionality
Page rendering and templates
Form processing and validation
Authentication and authorization
API endpoints and JSON responses
CRUD operations
Error handling
Database Operations
Model creation and updates
Data validation and constraints
Relationships and associations
Transaction handling
Data integrity
Security Features
CSRF token validation
Authentication flows
Authorization checks
Input sanitization
SQL injection prevention
Business Logic
Utility functions
Helper methods
Date and string formatting
Validation rules
Custom algorithms
For detailed guidance on what to test and testing strategies, see the TestBox Testing Code Coverage documentation.
Best Practices
For comprehensive testing best practices and advanced techniques, refer to the TestBox Testing documentation.
Legacy RocketUnit Testing Framework
Note: This section documents the legacy RocketUnit testing framework used in earlier Wheels versions. For new Wheels 3.0 applications, use the TestBox approach documented above. This information is maintained for reference and migration purposes.
Legacy RocketUnit Overview
Prior to Wheels 3.0, the framework used RocketUnit as its testing infrastructure. RocketUnit was a comprehensive testing framework that provided both unit testing and integration testing capabilities specifically tailored for Wheels applications.
At some point, your code is going to break. Upgrades, feature enhancements, and bug fixes are all part of the development lifecycle. Quite often with deadlines, you don't have the time to test the functionality of your entire application with every change you make.
The problem is that today's fix could be tomorrow's bug. What if there were an automated way of checking if that change you're making is going to break something? That's where writing tests for your application can be invaluable.
Legacy RocketUnit Features
RocketUnit provided a complete testing solution with the following features:
Unit Testing: Testing individual functions and methods in isolation
Integration Testing: Testing complete request flows and component interactions
Assertion Library: Comprehensive set of assertion methods for validation
Test Lifecycle Management: Setup and teardown methods for test preparation
Package Organization: Hierarchical test organization and execution
Multiple Output Formats: HTML, text, and custom reporting formats
Database Testing Support: Built-in database seeding and cleanup capabilities
Legacy Directory Structure
The legacy RocketUnit testing framework used a specific directory structure within your Wheels application:
your-app/
├── tests/
│ ├── Test.cfc # Parent test component (extends wheels.Test)
│ ├── functions/ # Function-level unit tests
│ │ └── Example.cfc # Example function tests
│ └── requests/ # Integration tests for request flows
│ └── Example.cfc # Example request tests
└── wheels/
├── Test.cfc # Core testing framework component
└── test/
└── functions.cfm # Testing framework implementation
Legacy Test Components
1. tests/Test.cfc - Parent Test Component
This was the base component that all test components should extend:
component extends="wheels.Test" hint="I am the parent test." {
function beforeAll() {
// Executes once before the test suite runs
}
function setup() {
// Executes before every test case
}
function teardown() {
// Executes after every test case
}
function afterAll() {
// Executes once after the test suite runs
}
}
Key Features:
Lifecycle Methods: Provided complete test lifecycle management
Framework Integration: Extended wheels.Test for full framework integration
Documentation Support: Included section and category metadata
2. tests/functions/Example.cfc - Function Testing
Example test file for testing controller, model, and global functions:
component extends="app.tests.Test" hint="I am an example test for functions." {
function packageSetup() {
// Executes once before this package's first test case
}
function packageTeardown() {
// Executes once after this package's last test case
}
function testExample() {
// Example test with simple assertion
assert('true');
}
}
Key Features:
Package-level Lifecycle: Setup and teardown for entire test packages
Unit Testing Focus: Designed for testing individual functions in isolation
Simple Assertions: Used basic assert() statements for validation
3. tests/requests/Example.cfc - Integration Testing
Example test file for integration testing where controllers, models, and other components work together:
component extends="app.tests.Test" hint="I am an example test for requests." {
function packageSetup() {
// Setup for integration test package
}
function packageTeardown() {
// Cleanup for integration test package
}
function testRequestFlow() {
// Test complete request processing
assert('true');
}
}
Key Features:
Integration Testing: Tested complete request flows and component interactions
Request Context: Simulated full HTTP request/response cycles
Component Interaction: Validated how controllers, models, and views worked together
Legacy Core Testing Framework
wheels/Test.cfc - Core Testing Component
The core testing framework component provided all testing functionality:
component hint="I am the testing framework for Wheels applications." {
public any function $runTest() {
// Main test execution method
}
public void function assert(required any expression) {
// Basic assertion method
}
public void function fail(string message = "") {
// Explicit test failure
}
public any function debug(required any expression) {
// Debug expression evaluation
}
public string function raised(required any expression) {
// Error catching and type return
}
}
wheels/test/functions.cfm - Framework Implementation
The comprehensive testing framework implementation included:
Assertion Functions:
assert(expression)
- Evaluated expressions and recorded failuresfail(message)
- Explicitly failed tests with custom messagesdebug(expression)
- Examined expressions during testingraised(expression)
- Caught and returned error types
Legacy Testing Philosophy and Structure
The RocketUnit framework followed these organizational principles:
Test Packages
Collections of test cases organized in directories, allowing for:
Logical grouping of related tests
Package-level setup and teardown
Hierarchical test execution
Modular test organization
Test Cases
Components containing multiple tests for specific functionality:
Focused on single areas of functionality
Contained multiple related test methods
Provided component-level lifecycle management
Extended the base Test component
Tests
Methods starting with "test" that contained assertions:
Named with descriptive test names (e.g.,
testUserValidation()
)Contained one or more assertion statements
Focused on specific behaviors or outcomes
Used simple assertion syntax
Assertions
Statements that should evaluate to true for tests to pass:
assert(expression)
- Basic true/false validationfail(message)
- Explicit test failure with custom messagedebug(expression)
- Expression evaluation for debuggingraised(expression)
- Error handling and type checking
Legacy Test Execution and Management
Running Tests
The RocketUnit framework supported multiple execution patterns:
Entire Test Packages:
# Run all tests in functions package
http://localhost:8080/wheels/packages/app?package=functions
# Run all tests in requests package
http://localhost:8080/wheels/packages/app?package=requests
Specific Test Cases:
# Run specific test component
http://localhost:8080/wheels/packages/app?package=functions&test=Example
# Run individual test method
http://localhost:8080/wheels/packages/app?package=functions&test=Example&method=testExample
With Filtering Options:
# Run tests with specific labels
http://localhost:8080/wheels/packages/app?labels=unit,fast
# Exclude slow tests
http://localhost:8080/wheels/packages/app?exclude=slow,integration
Test Types Supported
The framework supported different categories of tests:
Core Tests: Framework-level tests for Wheels components App Tests: Application-specific tests for your code Plugin Tests: Tests for Wheels plugins and extensions
Advanced Legacy Features
Database Seeding Capabilities:
Automatic test database population
Data cleanup between tests
Transaction-based test isolation
Custom seeding scripts
Test Environment Initialization:
Isolated test environment setup
Configuration override capabilities
Mock service integration
Resource allocation and cleanup
Debugging and Output Formatting:
Detailed test execution reports
Error stack traces and debugging info
Performance timing information
Custom output formatters
Error Handling and Reporting:
Comprehensive error capture
Test failure analysis
Exception type categorization
Detailed failure reporting
Legacy Usage Patterns
Test Organization Structure
tests/functions/ # Unit testing individual functions
tests/requests/ # Integration testing request flows
tests/models/ # Model-specific testing (optional)
tests/controllers/ # Controller-specific testing (optional)
Lifecycle Management Hierarchy
Suite Level:
beforeAll()
andafterAll()
for entire test suitePackage Level:
packageSetup()
andpackageTeardown()
for test packagesTest Case Level:
setup()
andteardown()
for individual test cases
Example Legacy Test Structure
component extends="app.tests.Test" {
function packageSetup() {
variables.testData = {
user: { username: "testuser", email: "[email protected]" }
};
}
function setup() {
variables.testUser = model("User").create(variables.testData.user);
}
function testUserCreation() {
assert('StructKeyExists(variables, "testUser")');
assert('variables.testUser.persisted()');
assert('variables.testUser.username eq "testuser"');
}
function testUserValidation() {
invalidUser = model("User").new({username: "", email: "invalid"});
assert('NOT invalidUser.valid()');
assert('invalidUser.hasErrors()');
}
function teardown() {
if (StructKeyExists(variables, "testUser")) {
variables.testUser.delete();
}
}
function packageTeardown() {
// Cleanup package-level resources
}
}
Legacy Framework Integration
The RocketUnit framework was tightly integrated with the Wheels framework, providing:
Model Testing Integration:
Automatic database transaction management
Model validation testing helpers
Relationship testing utilities
Database seeding and cleanup
Controller Testing Integration:
Request simulation capabilities
Response validation helpers
Session and cookie management
Route testing functionality
View Testing Integration:
Template rendering validation
Output content verification
Helper function testing
Layout and partial testing
Migration from Legacy RocketUnit to TestBox
When migrating from the legacy RocketUnit system to TestBox 5, consider the following mapping:
Syntax Migration
assert(expression)
→expect(result).toBeTrue()
fail(message)
→fail(message)
(same syntax)debug(expression)
→debug(expression)
(same syntax)Test methods → Wrapped in
describe()
andit()
blocks
Structure Migration
tests/functions/
→tests/specs/functions/
tests/requests/
→tests/specs/controllers/
Component extensions change from
app.tests.Test
totestbox.system.BaseSpec
Lifecycle Migration
packageSetup()
→beforeAll()
in describe blockpackageTeardown()
→afterAll()
in describe blocksetup()
→beforeEach()
in describe blockteardown()
→afterEach()
in describe block
Legacy Reference and Historical Context
The RocketUnit framework served the Wheels community well for many years, providing a solid foundation for test-driven development in CFML applications. While TestBox now provides more modern BDD/TDD capabilities, understanding the legacy system helps with:
Migration Planning: Understanding existing test structures for conversion
Historical Context: Appreciating the evolution of CFML testing frameworks
Legacy Maintenance: Supporting older Wheels applications still using RocketUnit
Framework Archaeology: Understanding the testing heritage of the Wheels framework
The comprehensive testing infrastructure provided by RocketUnit established many of the testing patterns and practices that continue in the modern TestBox implementation, ensuring continuity and familiarity for developers transitioning between the frameworks.
This comprehensive testing approach ensures your Wheels 3.0 application is thoroughly validated across all components, provides multi-format output for different environments, and supports various database configurations for complete coverage while maintaining reference information for legacy RocketUnit systems.
Running Legacy RocketUnit Tests in Wheels 3.0
If you already have application-level tests written with RocketUnit in a Wheels 2.x app, you don’t need to rewrite them immediately when upgrading to Wheels 3.0. Wheels 3.0 still provides backward compatibility for running RocketUnit tests alongside TestBox. To run your existing RocketUnit tests:
Copy the entire contents of that
tests/
folder.Paste the copied folder into your Wheels 3.0 application under
tests/RocketUnit/
.Run the legacy tests by visiting the RocketUnit runner in your browser: http://localhost:8080/wheels/legacy/app/tests
This approach lets you continue running your legacy RocketUnit test suite unchanged inside a Wheels 3.0 project while you gradually migrate to TestBox. It’s particularly useful for teams upgrading large applications where a complete migration cannot be done in one step.
Last updated
Was this helpful?