LogoLogo
HomeAPIBlog
3.0.0-SNAPSHOT
3.0.0-SNAPSHOT
  • INTRODUCTION
    • Getting Started
      • Running Local Development Servers
      • Beginner Tutorial: Hello World
      • Beginner Tutorial: Hello Database
      • Tutorial: Wheels, AJAX, and You
    • Frameworks and Wheels
    • Requirements
    • Manual Installation
    • Upgrading
    • Screencasts
  • Command Line Tools
    • CLI Overview
    • Quick Start Guide
    • Command Reference
      • Core Commands
        • wheels init
        • wheels info
        • wheels reload
        • wheels deps
        • wheels destroy
        • wheels watch
      • Code Generation
        • wheels generate app
        • wheels generate app-wizard
        • wheels generate controller
        • wheels generate model
        • wheels generate view
        • wheels generate property
        • wheels generate route
        • wheels generate resource
        • wheels generate api-resource
        • wheels generate frontend
        • wheels generate test
        • wheels generate snippets
        • wheels scaffold
      • Database Commands
        • wheels dbmigrate info
        • wheels dbmigrate latest
        • wheels dbmigrate up
        • wheels dbmigrate down
        • wheels dbmigrate reset
        • wheels dbmigrate exec
        • wheels dbmigrate create blank
        • wheels dbmigrate create table
        • wheels dbmigrate create column
        • wheels dbmigrate remove table
        • wheels db schema
        • wheels db seed
      • Testing Commands
        • wheels test
        • wheels test run
        • wheels test coverage
        • wheels test debug
      • Configuration Commands
        • wheels config list
        • wheels config set
        • wheels config env
      • Environment Management
        • wheels env
        • wheels env setup
        • wheels env list
        • wheels env switch
      • Plugin Management
        • wheels plugins
        • wheels plugins list
        • wheels plugins install
        • wheels plugins remove
      • Code Analysis
        • wheels analyze
        • wheels analyze code
        • wheels analyze performance
        • wheels analyze security
      • Security Commands
        • wheels security
        • wheels security scan
      • Performance Commands
        • wheels optimize
        • wheels optimize performance
      • Documentation Commands
        • wheels docs
        • wheels docs generate
        • wheels docs serve
      • CI/CD Commands
        • wheels ci init
      • Docker Commands
        • wheels docker init
        • wheels docker deploy
      • Deployment Commands
        • wheels deploy
        • wheels deploy audit
        • wheels deploy exec
        • wheels deploy hooks
        • wheels deploy init
        • wheels deploy lock
        • wheels deploy logs
        • wheels deploy proxy
        • wheels deploy push
        • wheels deploy rollback
        • wheels deploy secrets
        • wheels deploy setup
        • wheels deploy status
        • wheels deploy stop
    • CLI Development Guides
      • Creating Commands
      • Service Architecture
      • Migrations Guide
      • Testing Guide
  • Working with Wheels
    • Conventions
    • Configuration and Defaults
    • Directory Structure
    • Switching Environments
    • Testing Your Application
    • Using the Test Environment
    • Contributing to Wheels
    • Submitting Pull Requests
    • Documenting your Code
  • Handling Requests with Controllers
    • Request Handling
    • Rendering Content
    • Redirecting Users
    • Sending Files
    • Sending Email
    • Responding with Multiple Formats
    • Using the Flash
    • Using Filters
    • Verification
    • Event Handlers
    • Routing
    • URL Rewriting
      • Apache
      • IIS
      • Tomcat
      • Nginx
    • Obfuscating URLs
    • Caching
    • Nesting Controllers
    • CORS Requests
  • Displaying Views to Users
    • Pages
    • Partials
    • Linking Pages
    • Layouts
    • Form Helpers and Showing Errors
    • Displaying Links for Pagination
    • Date, Media, and Text Helpers
    • Creating Custom View Helpers
    • Localization
  • Database Interaction Through Models
    • Object Relational Mapping
    • Creating Records
    • Reading Records
    • Updating Records
    • Deleting Records
    • Column Statistics
    • Dynamic Finders
    • Getting Paginated Data
    • Associations
    • Nested Properties
    • Object Validation
    • Object Callbacks
    • Calculated Properties
    • Transactions
    • Dirty Records
    • Soft Delete
    • Automatic Time Stamps
    • Database Migrations
      • Migrations in Production
    • Using Multiple Data Sources
  • Plugins
    • Installing and Using Plugins
    • Developing Plugins
    • Publishing Plugins
  • Project Documentation
    • Overview
  • External Links
    • Source Code
    • Issue Tracker
    • Sponsor Us
    • Community
Powered by GitBook
LogoLogo
On this page
  • Overview
  • Test Structure
  • Directory Layout
  • Test File Naming
  • Writing Tests
  • Basic Test Structure
  • Model Testing
  • Controller Testing
  • Integration Testing
  • Test Helpers
  • Creating Test Factories
  • Test Data Management
  • Running Tests
  • Basic Commands
  • Watch Mode
  • Filtering Tests
  • Code Coverage
  • Generate Coverage Report
  • Coverage Configuration
  • Test Configuration
  • Test Suite Configuration
  • Environment Variables
  • Testing Best Practices
  • 1. Test Organization
  • 2. Test Isolation
  • 3. Descriptive Tests
  • 4. AAA Pattern
  • Continuous Integration
  • GitHub Actions
  • Pre-commit Hooks
  • Debugging Tests
  • Using Debug Output
  • Interactive Debugging
  • Performance Testing
  • Load Testing
  • Common Testing Patterns
  • Testing Private Methods
  • Mocking External Services
  • Docker-Based Testing
  • Quick Start with Docker
  • TestUI Features
  • Container Management
  • Docker Profiles
  • Running Tests via Docker
  • Database Testing
  • See Also

Was this helpful?

Edit on GitHub
Export as PDF
  1. Command Line Tools
  2. CLI Development Guides

Testing Guide

Comprehensive guide to testing in Wheels applications using the CLI.

Overview

Wheels CLI provides robust testing capabilities through TestBox integration, offering:

  • Unit and integration testing

  • BDD-style test writing

  • Watch mode for continuous testing

  • Code coverage reporting

  • Parallel test execution

  • Docker-based testing across multiple engines and databases

Test Structure

Directory Layout

/tests/
├── Application.cfc          # Test suite configuration
├── specs/                   # Test specifications (alternative to folders below)
├── unit/                    # Unit tests
│   ├── models/             # Model tests
│   ├── controllers/        # Controller tests
│   └── services/           # Service tests
├── integration/            # Integration tests
├── fixtures/               # Test data files
└── helpers/                # Test utilities

Test File Naming

Follow these conventions:

  • Model tests: UserTest.cfc or UserSpec.cfc

  • Controller tests: UsersControllerTest.cfc

  • Integration tests: UserFlowTest.cfc

Writing Tests

Basic Test Structure

// tests/unit/models/UserTest.cfc
component extends="testbox.system.BaseSpec" {
    
    function run() {
        describe("User Model", function() {
            
            beforeEach(function() {
                // Setup before each test
                variables.user = model("User").new();
            });
            
            afterEach(function() {
                // Cleanup after each test
            });
            
            it("validates email presence", function() {
                variables.user.email = "";
                expect(variables.user.valid()).toBeFalse();
                expect(variables.user.errors).toHaveKey("email");
            });
            
            it("validates email format", function() {
                variables.user.email = "invalid-email";
                expect(variables.user.valid()).toBeFalse();
                expect(variables.user.errors.email).toInclude("valid email");
            });
            
        });
    }
    
}

Model Testing

component extends="testbox.system.BaseSpec" {
    
    function run() {
        describe("Product Model", function() {
            
            describe("Validations", function() {
                it("requires a name", function() {
                    var product = model("Product").new();
                    expect(product.valid()).toBeFalse();
                    expect(product.errors).toHaveKey("name");
                });
                
                it("requires price to be positive", function() {
                    var product = model("Product").new(
                        name = "Test Product",
                        price = -10
                    );
                    expect(product.valid()).toBeFalse();
                    expect(product.errors.price).toInclude("greater than 0");
                });
            });
            
            describe("Associations", function() {
                it("has many reviews", function() {
                    var product = model("Product").findOne();
                    expect(product).toHaveKey("reviews");
                    expect(product.reviews()).toBeQuery();
                });
            });
            
            describe("Scopes", function() {
                it("filters active products", function() {
                    // Create test data
                    model("Product").create(name="Active", active=true);
                    model("Product").create(name="Inactive", active=false);
                    
                    var activeProducts = model("Product").active().findAll();
                    expect(activeProducts.recordCount).toBe(1);
                    expect(activeProducts.name).toBe("Active");
                });
            });
            
        });
    }
    
}

Controller Testing

component extends="testbox.system.BaseSpec" {
    
    function beforeAll() {
        // Setup test request context
        variables.mockController = prepareMock(createObject("component", "controllers.Products"));
    }
    
    function run() {
        describe("Products Controller", function() {
            
            describe("index action", function() {
                it("returns all products", function() {
                    // Setup
                    var products = queryNew("id,name", "integer,varchar", [
                        [1, "Product 1"],
                        [2, "Product 2"]
                    ]);
                    
                    mockController.$("model").$args("Product").$returns(
                        mockModel.$("findAll").$returns(products)
                    );
                    
                    // Execute
                    mockController.index();
                    
                    // Assert
                    expect(mockController.products).toBe(products);
                    expect(mockController.products.recordCount).toBe(2);
                });
            });
            
            describe("create action", function() {
                it("creates product with valid data", function() {
                    // Setup params
                    mockController.params = {
                        product: {
                            name: "New Product",
                            price: 99.99
                        }
                    };
                    
                    // Mock successful save
                    var mockProduct = createEmptyMock("models.Product");
                    mockProduct.$("save").$returns(true);
                    mockProduct.$("id", 123);
                    
                    mockController.$("model").$args("Product").$returns(
                        createMock("models.Product").$("new").$returns(mockProduct)
                    );
                    
                    // Execute
                    mockController.create();
                    
                    // Assert
                    expect(mockController.flashMessages.success).toInclude("created successfully");
                    expect(mockController.redirectTo.action).toBe("show");
                    expect(mockController.redirectTo.key).toBe(123);
                });
            });
            
        });
    }
    
}

Integration Testing

component extends="testbox.system.BaseSpec" {
    
    function run() {
        describe("User Registration Flow", function() {
            
            it("allows new user to register", function() {
                // Visit registration page
                var event = execute(event="users.new", renderResults=true);
                expect(event.getRenderedContent()).toInclude("Register");
                
                // Submit registration form
                var event = execute(
                    event = "users.create",
                    eventArguments = {
                        user: {
                            email: "test@example.com",
                            password: "SecurePass123!",
                            passwordConfirmation: "SecurePass123!"
                        }
                    }
                );
                
                // Verify user created
                var user = model("User").findOne(where="email='test@example.com'");
                expect(user).toBeObject();
                
                // Verify logged in
                expect(session.userId).toBe(user.id);
                
                // Verify redirect
                expect(event.getValue("relocate_URI")).toBe("/dashboard");
            });
            
        });
    }
    
}

Test Helpers

Creating Test Factories

// tests/helpers/Factories.cfc
component {
    
    function createUser(struct overrides = {}) {
        var defaults = {
            email: "user#createUUID()#@test.com",
            password: "password123",
            firstName: "Test",
            lastName: "User"
        };
        
        defaults.append(arguments.overrides);
        return model("User").create(defaults);
    }
    
    function createProduct(struct overrides = {}) {
        var defaults = {
            name: "Product #createUUID()#",
            price: randRange(10, 100),
            stock: randRange(0, 50)
        };
        
        defaults.append(arguments.overrides);
        return model("Product").create(defaults);
    }
    
}

Test Data Management

// tests/helpers/TestDatabase.cfc
component {
    
    function setUp() {
        // Start transaction
        transaction action="begin";
    }
    
    function tearDown() {
        // Rollback transaction
        transaction action="rollback";
    }
    
    function clean() {
        // Clean specific tables
        queryExecute("DELETE FROM users WHERE email LIKE '%@test.com'");
        queryExecute("DELETE FROM products WHERE name LIKE 'Test%'");
    }
    
    function loadFixtures(required string name) {
        var fixtures = deserializeJSON(
            fileRead("/tests/fixtures/#arguments.name#.json")
        );
        
        for (var record in fixtures) {
            queryExecute(
                "INSERT INTO #arguments.name# (#structKeyList(record)#) 
                 VALUES (#structKeyList(record, ':')#)",
                record
            );
        }
    }
    
}

Running Tests

Basic Commands

# Run all tests
wheels test run

# Run specific test file
wheels test run tests/unit/models/UserTest.cfc

# Run tests in directory
wheels test run tests/unit/models/

# Run with specific reporter
wheels test run --reporter=json
wheels test run --reporter=junit --outputFile=results.xml

Watch Mode

# Watch for changes and rerun tests
wheels test run --watch

# Watch specific directory
wheels test run tests/models --watch

# Watch with custom debounce
wheels test run --watch --watchDelay=1000

Filtering Tests

# Run by test bundles
wheels test run --bundles=models,controllers

# Run by labels
wheels test run --labels=critical

# Run by test name pattern
wheels test run --filter="user"

# Exclude patterns
wheels test run --excludes="slow,integration"

Code Coverage

Generate Coverage Report

# Generate HTML coverage report
wheels test coverage

# With custom output directory
wheels test coverage --outputDir=coverage-reports

# Include only specific paths
wheels test coverage --includes="models/,controllers/"

Coverage Configuration

In tests/Application.cfc:

this.coverage = {
    enabled: true,
    includes: ["models", "controllers"],
    excludes: ["tests", "wheels"],
    outputDir: expandPath("/tests/coverage/"),
    reportFormats: ["html", "json"]
};

Test Configuration

Test Suite Configuration

// tests/Application.cfc
component {
    
    this.name = "WheelsTestSuite" & Hash(GetCurrentTemplatePath());
    
    // Test datasource
    this.datasources["test"] = {
        url: "jdbc:h2:mem:test;MODE=MySQL",
        driver: "org.h2.Driver"
    };
    this.datasource = "test";
    
    // TestBox settings
    this.testbox = {
        bundles: ["tests"],
        recurse: true,
        reporter: "simple",
        reportpath: "/tests/results",
        runner: ["tests/runner.cfm"],
        labels: [],
        options: {}
    };
    
}

Environment Variables

# Set test environment
export WHEELS_ENV=testing

# Set test datasource
export WHEELS_TEST_DATASOURCE=myapp_test

# Enable verbose output
export TESTBOX_VERBOSE=true

Testing Best Practices

1. Test Organization

tests/
├── unit/              # Fast, isolated tests
│   ├── models/       # One file per model
│   └── services/     # Service layer tests
├── integration/      # Tests with dependencies
└── e2e/             # End-to-end tests

2. Test Isolation

describe("User Model", function() {
    
    beforeEach(function() {
        // Fresh instance for each test
        variables.user = model("User").new();
        
        // Clear caches
        application.wheels.cache.queries = {};
    });
    
    afterEach(function() {
        // Clean up test data
        if (isDefined("variables.user.id")) {
            variables.user.delete();
        }
    });
    
});

3. Descriptive Tests

// Good: Descriptive test names
it("validates email format with standard RFC 5322 regex", function() {
    // test implementation
});

it("prevents duplicate email addresses case-insensitively", function() {
    // test implementation
});

// Bad: Vague test names
it("works", function() {
    // test implementation
});

4. AAA Pattern

it("calculates order total with tax", function() {
    // Arrange
    var order = createOrder();
    var item1 = createOrderItem(price: 100, quantity: 2);
    var item2 = createOrderItem(price: 50, quantity: 1);
    order.addItem(item1);
    order.addItem(item2);
    
    // Act
    var total = order.calculateTotal(taxRate: 0.08);
    
    // Assert
    expect(total).toBe(270); // (200 + 50) * 1.08
});

Continuous Integration

GitHub Actions

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup CommandBox
      uses: Ortus-Solutions/setup-commandbox@v2.0.0
    
    - name: Install dependencies
      run: box install
    
    - name: Run tests
      run: |
        box server start
        wheels test run --reporter=junit --outputFile=test-results.xml
    
    - name: Upload test results
      uses: actions/upload-artifact@v2
      with:
        name: test-results
        path: test-results.xml
    
    - name: Generate coverage
      run: wheels test coverage
    
    - name: Upload coverage
      uses: codecov/codecov-action@v1

Pre-commit Hooks

#!/bin/bash
# .git/hooks/pre-commit

echo "Running tests..."
wheels test run --labels=unit

if [ $? -ne 0 ]; then
    echo "Tests failed! Commit aborted."
    exit 1
fi

echo "Running linter..."
wheels analyze code

if [ $? -ne 0 ]; then
    echo "Code quality check failed!"
    exit 1
fi

Debugging Tests

Using Debug Output

it("processes data correctly", function() {
    var result = processData(testData);
    
    // Debug output
    debug(result);
    writeDump(var=result, abort=false);
    
    // Conditional debugging
    if (request.debug ?: false) {
        writeOutput("Result: #serializeJSON(result)#");
    }
    
    expect(result.status).toBe("success");
});

Interactive Debugging

# Run specific test with debugging
wheels test debug tests/unit/models/UserTest.cfc

# Enable verbose mode
wheels test run --verbose

# Show SQL queries
wheels test run --showSQL

Performance Testing

Load Testing

describe("Performance", function() {
    
    it("handles 1000 concurrent users", function() {
        var threads = [];
        
        for (var i = 1; i <= 1000; i++) {
            arrayAppend(threads, function() {
                var result = model("Product").findAll();
                return result.recordCount;
            });
        }
        
        var start = getTickCount();
        var results = parallel(threads);
        var duration = getTickCount() - start;
        
        expect(duration).toBeLT(5000); // Less than 5 seconds
        expect(arrayLen(results)).toBe(1000);
    });
    
});

Common Testing Patterns

Testing Private Methods

it("tests private method", function() {
    var user = model("User").new();
    
    // Use makePublic() for testing
    makePublic(user, "privateMethod");
    
    var result = user.privateMethod();
    expect(result).toBe("expected");
});

Mocking External Services

it("sends email on user creation", function() {
    // Mock email service
    var mockMailer = createEmptyMock("services.Mailer");
    mockMailer.$("send").$returns(true);
    
    // Inject mock
    var user = model("User").new();
    user.$property("mailer", mockMailer);
    
    // Test
    user.save();
    
    // Verify
    expect(mockMailer.$times("send")).toBe(1);
    expect(mockMailer.$callLog().send[1].to).toBe(user.email);
});

Docker-Based Testing

Wheels provides a comprehensive Docker environment for testing across multiple CFML engines and databases.

Quick Start with Docker

# Start the TestUI and all test containers
docker compose --profile all up -d

# Access the TestUI
open http://localhost:3000

TestUI Features

The modern TestUI provides:

  • Visual Test Runner: Run and monitor tests in real-time

  • Container Management: Start/stop containers directly from the UI

  • Multi-Engine Support: Test on Lucee 5/6 and Adobe ColdFusion 2018/2021/2023

  • Multi-Database Support: MySQL, PostgreSQL, SQL Server, H2, and Oracle

  • Pre-flight Checks: Ensures all services are running before tests

  • Test History: Track test results over time

Container Management

The TestUI includes an API server that allows you to:

  1. Click on any stopped engine or database to start it

  2. Monitor container health and status

  3. View real-time logs

  4. No terminal required for basic operations

Docker Profiles

Use profiles to start specific combinations:

# Just the UI
docker compose --profile ui up -d

# Quick test setup (Lucee 5 + MySQL)
docker compose --profile quick-test up -d

# All Lucee engines
docker compose --profile lucee up -d

# All Adobe engines
docker compose --profile adobe up -d

# All databases
docker compose --profile db up -d

Running Tests via Docker

# Using the CLI inside a container
docker exec -it wheels-lucee5-1 wheels test run

# Direct URL access
curl http://localhost:60005/wheels/testbox?format=json&db=mysql

Database Testing

Test against different databases by using the db parameter:

# MySQL
wheels test run --db=mysql

# PostgreSQL
wheels test run --db=postgres

# SQL Server
wheels test run --db=sqlserver

# H2 (Lucee only)
wheels test run --db=h2

# Oracle
wheels test run --db=oracle

See Also

PreviousMigrations GuideNextConventions

Last updated 2 days ago

Was this helpful?

- Test execution command

- Coverage generation

- Generate test files

- Complete TestBox guide

- Detailed Docker testing documentation

wheels test run
wheels test coverage
wheels generate test
TestBox Documentation
Docker Testing Guide