Repository files navigation

4D Unit Testing Framework

A comprehensive unit testing framework for the 4D platform with test tagging, filtering, and CI/CD integration.

Key Features

  • Auto test discovery - Finds test classes ending with "Test"
  • Test tagging - Organize tests with // #tags: unit, integration, slow
  • Flexible filtering - Run specific tests by name, pattern, or tags
  • Multiple output formats - Human-readable, JSON, and JUnit XML
  • CI/CD ready - Structured JSON / JUnit XML output for automated testing
  • Transaction management - Automatic test isolation with rollback
  • Subtests - Run table-driven tests with t.run
  • Host runtime-error capture - When used as a component, captures and reports host-side runtime errors (per-test and global) with stack chains

Quick Example

// UserServiceTest.4dmClass constructor()
// #tags: unit, fastFunction test_user_creation($t : cs.Testing)
 var$user : Object
$user:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.isNotNull($t; $user.name; "User should have a name")
$t.assert.areEqual($t; "John"; $user.name; "User name should be correct")
// Compare complex objects with deep equality var$expected : Object
$expected:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.areDeepEqual($t; $expected; $user; "User object should match expected structure")
// #tags: integration, databaseFunction test_user_persistence($t : cs.Testing)
// Database operations are automatically rolled back var$user : cs.UsersEntity$user:=ds.Users.new()
$user.name:="John"$user.email:="john@example.com" var$result : Object
$result:=$user.save()
$t.assert.isTrue($t; $result.success; "User should be saved successfully")

Setup

Create a project method (e.g., "test") with this code:

var$runner : cs.Testing.TestRunner$runner:=cs.Testing.TestRunner.new(cs)
$runner.run()

Running Tests

# Run all tests
tool4d --project YourProject.4DProject --startup-method "test"# Run with JSON output
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json"# Write JSON to a file (clean output, even with debug logs on stdout)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json outputPath=test-results/report.json"# Include call chains on failed tests in terse JSON without going full verbose
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json callchain=true"# JUnit XML output (CI-friendly: failures, errors, skipped, system-err)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=junit outputPath=test-results/junit.xml"# Run specific tests
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest.test_user_creation"# Filter by tags
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit excludeTags=slow"

Table-Driven Tests

Use subtests to build table-driven tests. Each call to t.run executes the provided function with a fresh testing context. If a subtest fails, the parent test is marked as failed. Subtests run with the same This object as the parent test, so helper methods and state remain accessible. Pass optional data as the third argument when the test logic lives in a separate method.

Function test_math($t : cs.Testing)
 var$cases : Collection
$cases:=[New object("name"; "1+1"; "in"; 1; "want"; 2)]
 var$case : Object
 For each ($case; $cases)
$t.run($case.name; This._checkMathCase; $case)
 End for eachFunction _checkMathCase($t : cs.Testing; $case : Object)
$t.assert.areEqual($t; $case.want; $case.in+1; "math works")

Output

Human format:

 ✓ test_user_creation (1ms)
✓ test_user_persistence (5ms)
2 tests passed

JSON format (terse — default with format=json):

{
"tests": 2,
"passed": 2,
"failed": 0,
"skipped": 0,
"duration": 6,
"rate": 100.0,
"status": "ok",
"globalErrorCount": 0,
"globalErrors": [],
"testResults": [ /* per-test entries with assertions[] and runtimeErrors[] */ ],"failures": [ /* one entry per failed test, includes callChain when verbose=true or callchain=true */ ]
}

External (non-test) runtime errors captured during the run live in globalErrors[] / globalErrorCount. JUnit output reports the same data in a <system-err> block.

Capturing Host Runtime Errors

When the testing framework is loaded as a component into a host project, host-side code (triggers, workers, methods called by tests) can raise runtime errors that the component's own ON ERR CALL cannot reach. To capture them:

  1. Define TestErrorHandler and TestGlobalErrorHandler project methods in the host that push {code, text, method, line, message, processNumber, context, isLocal, callChainJSON, ...} records onto Storage.testErrors (a shared collection).

  2. In the host's test entry point, install both handlers and pass the host's Storage to the component:

    // RunTests.4dmON ERR CALL("TestErrorHandler")
    ON ERR CALL("TestGlobalErrorHandler"; 1)
    Testing_RunTestsWithCs(cs; Storage; $userParams)
  3. The component drains Storage.testErrors per-test (matched by processNumber) and globally, and surfaces them in every output format: per-test runtimeErrors[] in JSON, top-level globalErrors[], and JUnit <system-err>.

Documentation

License

MIT License

About

A 4D unit testing component

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

4D Unit Testing Framework

A comprehensive unit testing framework for the 4D platform with test tagging, filtering, and CI/CD integration.

Key Features

  • Auto test discovery - Finds test classes ending with "Test"
  • Test tagging - Organize tests with // #tags: unit, integration, slow
  • Flexible filtering - Run specific tests by name, pattern, or tags
  • Multiple output formats - Human-readable, JSON, and JUnit XML
  • CI/CD ready - Structured JSON / JUnit XML output for automated testing
  • Transaction management - Automatic test isolation with rollback
  • Subtests - Run table-driven tests with t.run
  • Host runtime-error capture - When used as a component, captures and reports host-side runtime errors (per-test and global) with stack chains

Quick Example

// UserServiceTest.4dmClass constructor()
// #tags: unit, fastFunction test_user_creation($t : cs.Testing)
 var$user : Object
$user:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.isNotNull($t; $user.name; "User should have a name")
$t.assert.areEqual($t; "John"; $user.name; "User name should be correct")
// Compare complex objects with deep equality var$expected : Object
$expected:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.areDeepEqual($t; $expected; $user; "User object should match expected structure")
// #tags: integration, databaseFunction test_user_persistence($t : cs.Testing)
// Database operations are automatically rolled back var$user : cs.UsersEntity$user:=ds.Users.new()
$user.name:="John"$user.email:="john@example.com" var$result : Object
$result:=$user.save()
$t.assert.isTrue($t; $result.success; "User should be saved successfully")

Setup

Create a project method (e.g., "test") with this code:

var$runner : cs.Testing.TestRunner$runner:=cs.Testing.TestRunner.new(cs)
$runner.run()

Running Tests

# Run all tests
tool4d --project YourProject.4DProject --startup-method "test"# Run with JSON output
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json"# Write JSON to a file (clean output, even with debug logs on stdout)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json outputPath=test-results/report.json"# Include call chains on failed tests in terse JSON without going full verbose
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json callchain=true"# JUnit XML output (CI-friendly: failures, errors, skipped, system-err)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=junit outputPath=test-results/junit.xml"# Run specific tests
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest.test_user_creation"# Filter by tags
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit excludeTags=slow"

Table-Driven Tests

Use subtests to build table-driven tests. Each call to t.run executes the provided function with a fresh testing context. If a subtest fails, the parent test is marked as failed. Subtests run with the same This object as the parent test, so helper methods and state remain accessible. Pass optional data as the third argument when the test logic lives in a separate method.

Function test_math($t : cs.Testing)
 var$cases : Collection
$cases:=[New object("name"; "1+1"; "in"; 1; "want"; 2)]
 var$case : Object
 For each ($case; $cases)
$t.run($case.name; This._checkMathCase; $case)
 End for eachFunction _checkMathCase($t : cs.Testing; $case : Object)
$t.assert.areEqual($t; $case.want; $case.in+1; "math works")

Output

Human format:

 ✓ test_user_creation (1ms)
✓ test_user_persistence (5ms)
2 tests passed

JSON format (terse — default with format=json):

{
"tests": 2,
"passed": 2,
"failed": 0,
"skipped": 0,
"duration": 6,
"rate": 100.0,
"status": "ok",
"globalErrorCount": 0,
"globalErrors": [],
"testResults": [ /* per-test entries with assertions[] and runtimeErrors[] */ ],"failures": [ /* one entry per failed test, includes callChain when verbose=true or callchain=true */ ]
}

External (non-test) runtime errors captured during the run live in globalErrors[] / globalErrorCount. JUnit output reports the same data in a <system-err> block.

Capturing Host Runtime Errors

When the testing framework is loaded as a component into a host project, host-side code (triggers, workers, methods called by tests) can raise runtime errors that the component's own ON ERR CALL cannot reach. To capture them:

  1. Define TestErrorHandler and TestGlobalErrorHandler project methods in the host that push {code, text, method, line, message, processNumber, context, isLocal, callChainJSON, ...} records onto Storage.testErrors (a shared collection).

  2. In the host's test entry point, install both handlers and pass the host's Storage to the component:

    // RunTests.4dmON ERR CALL("TestErrorHandler")
    ON ERR CALL("TestGlobalErrorHandler"; 1)
    Testing_RunTestsWithCs(cs; Storage; $userParams)
  3. The component drains Storage.testErrors per-test (matched by processNumber) and globally, and surfaces them in every output format: per-test runtimeErrors[] in JSON, top-level globalErrors[], and JUnit <system-err>.

Documentation

License

MIT License

About

A 4D unit testing component

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

4D Unit Testing Framework

A comprehensive unit testing framework for the 4D platform with test tagging, filtering, and CI/CD integration.

Key Features

  • Auto test discovery - Finds test classes ending with "Test"
  • Test tagging - Organize tests with // #tags: unit, integration, slow
  • Flexible filtering - Run specific tests by name, pattern, or tags
  • Multiple output formats - Human-readable, JSON, and JUnit XML
  • CI/CD ready - Structured JSON / JUnit XML output for automated testing
  • Transaction management - Automatic test isolation with rollback
  • Subtests - Run table-driven tests with t.run
  • Host runtime-error capture - When used as a component, captures and reports host-side runtime errors (per-test and global) with stack chains

Quick Example

// UserServiceTest.4dmClass constructor()
// #tags: unit, fastFunction test_user_creation($t : cs.Testing)
 var$user : Object
$user:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.isNotNull($t; $user.name; "User should have a name")
$t.assert.areEqual($t; "John"; $user.name; "User name should be correct")
// Compare complex objects with deep equality var$expected : Object
$expected:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.areDeepEqual($t; $expected; $user; "User object should match expected structure")
// #tags: integration, databaseFunction test_user_persistence($t : cs.Testing)
// Database operations are automatically rolled back var$user : cs.UsersEntity$user:=ds.Users.new()
$user.name:="John"$user.email:="john@example.com" var$result : Object
$result:=$user.save()
$t.assert.isTrue($t; $result.success; "User should be saved successfully")

Setup

Create a project method (e.g., "test") with this code:

var$runner : cs.Testing.TestRunner$runner:=cs.Testing.TestRunner.new(cs)
$runner.run()

Running Tests

# Run all tests
tool4d --project YourProject.4DProject --startup-method "test"# Run with JSON output
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json"# Write JSON to a file (clean output, even with debug logs on stdout)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json outputPath=test-results/report.json"# Include call chains on failed tests in terse JSON without going full verbose
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json callchain=true"# JUnit XML output (CI-friendly: failures, errors, skipped, system-err)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=junit outputPath=test-results/junit.xml"# Run specific tests
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest.test_user_creation"# Filter by tags
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit excludeTags=slow"

Table-Driven Tests

Use subtests to build table-driven tests. Each call to t.run executes the provided function with a fresh testing context. If a subtest fails, the parent test is marked as failed. Subtests run with the same This object as the parent test, so helper methods and state remain accessible. Pass optional data as the third argument when the test logic lives in a separate method.

Function test_math($t : cs.Testing)
 var$cases : Collection
$cases:=[New object("name"; "1+1"; "in"; 1; "want"; 2)]
 var$case : Object
 For each ($case; $cases)
$t.run($case.name; This._checkMathCase; $case)
 End for eachFunction _checkMathCase($t : cs.Testing; $case : Object)
$t.assert.areEqual($t; $case.want; $case.in+1; "math works")

Output

Human format:

 ✓ test_user_creation (1ms)
✓ test_user_persistence (5ms)
2 tests passed

JSON format (terse — default with format=json):

{
"tests": 2,
"passed": 2,
"failed": 0,
"skipped": 0,
"duration": 6,
"rate": 100.0,
"status": "ok",
"globalErrorCount": 0,
"globalErrors": [],
"testResults": [ /* per-test entries with assertions[] and runtimeErrors[] */ ],"failures": [ /* one entry per failed test, includes callChain when verbose=true or callchain=true */ ]
}

External (non-test) runtime errors captured during the run live in globalErrors[] / globalErrorCount. JUnit output reports the same data in a <system-err> block.

Capturing Host Runtime Errors

When the testing framework is loaded as a component into a host project, host-side code (triggers, workers, methods called by tests) can raise runtime errors that the component's own ON ERR CALL cannot reach. To capture them:

  1. Define TestErrorHandler and TestGlobalErrorHandler project methods in the host that push {code, text, method, line, message, processNumber, context, isLocal, callChainJSON, ...} records onto Storage.testErrors (a shared collection).

  2. In the host's test entry point, install both handlers and pass the host's Storage to the component:

    // RunTests.4dmON ERR CALL("TestErrorHandler")
    ON ERR CALL("TestGlobalErrorHandler"; 1)
    Testing_RunTestsWithCs(cs; Storage; $userParams)
  3. The component drains Storage.testErrors per-test (matched by processNumber) and globally, and surfaces them in every output format: per-test runtimeErrors[] in JSON, top-level globalErrors[], and JUnit <system-err>.

Documentation

License

MIT License

About

A 4D unit testing component

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

4D Unit Testing Framework

A comprehensive unit testing framework for the 4D platform with test tagging, filtering, and CI/CD integration.

Key Features

  • Auto test discovery - Finds test classes ending with "Test"
  • Test tagging - Organize tests with // #tags: unit, integration, slow
  • Flexible filtering - Run specific tests by name, pattern, or tags
  • Multiple output formats - Human-readable, JSON, and JUnit XML
  • CI/CD ready - Structured JSON / JUnit XML output for automated testing
  • Transaction management - Automatic test isolation with rollback
  • Subtests - Run table-driven tests with t.run
  • Host runtime-error capture - When used as a component, captures and reports host-side runtime errors (per-test and global) with stack chains

Quick Example

// UserServiceTest.4dmClass constructor()
// #tags: unit, fastFunction test_user_creation($t : cs.Testing)
 var$user : Object
$user:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.isNotNull($t; $user.name; "User should have a name")
$t.assert.areEqual($t; "John"; $user.name; "User name should be correct")
// Compare complex objects with deep equality var$expected : Object
$expected:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.areDeepEqual($t; $expected; $user; "User object should match expected structure")
// #tags: integration, databaseFunction test_user_persistence($t : cs.Testing)
// Database operations are automatically rolled back var$user : cs.UsersEntity$user:=ds.Users.new()
$user.name:="John"$user.email:="john@example.com" var$result : Object
$result:=$user.save()
$t.assert.isTrue($t; $result.success; "User should be saved successfully")

Setup

Create a project method (e.g., "test") with this code:

var$runner : cs.Testing.TestRunner$runner:=cs.Testing.TestRunner.new(cs)
$runner.run()

Running Tests

# Run all tests
tool4d --project YourProject.4DProject --startup-method "test"# Run with JSON output
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json"# Write JSON to a file (clean output, even with debug logs on stdout)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json outputPath=test-results/report.json"# Include call chains on failed tests in terse JSON without going full verbose
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json callchain=true"# JUnit XML output (CI-friendly: failures, errors, skipped, system-err)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=junit outputPath=test-results/junit.xml"# Run specific tests
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest.test_user_creation"# Filter by tags
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit excludeTags=slow"

Table-Driven Tests

Use subtests to build table-driven tests. Each call to t.run executes the provided function with a fresh testing context. If a subtest fails, the parent test is marked as failed. Subtests run with the same This object as the parent test, so helper methods and state remain accessible. Pass optional data as the third argument when the test logic lives in a separate method.

Function test_math($t : cs.Testing)
 var$cases : Collection
$cases:=[New object("name"; "1+1"; "in"; 1; "want"; 2)]
 var$case : Object
 For each ($case; $cases)
$t.run($case.name; This._checkMathCase; $case)
 End for eachFunction _checkMathCase($t : cs.Testing; $case : Object)
$t.assert.areEqual($t; $case.want; $case.in+1; "math works")

Output

Human format:

 ✓ test_user_creation (1ms)
✓ test_user_persistence (5ms)
2 tests passed

JSON format (terse — default with format=json):

{
"tests": 2,
"passed": 2,
"failed": 0,
"skipped": 0,
"duration": 6,
"rate": 100.0,
"status": "ok",
"globalErrorCount": 0,
"globalErrors": [],
"testResults": [ /* per-test entries with assertions[] and runtimeErrors[] */ ],"failures": [ /* one entry per failed test, includes callChain when verbose=true or callchain=true */ ]
}

External (non-test) runtime errors captured during the run live in globalErrors[] / globalErrorCount. JUnit output reports the same data in a <system-err> block.

Capturing Host Runtime Errors

When the testing framework is loaded as a component into a host project, host-side code (triggers, workers, methods called by tests) can raise runtime errors that the component's own ON ERR CALL cannot reach. To capture them:

  1. Define TestErrorHandler and TestGlobalErrorHandler project methods in the host that push {code, text, method, line, message, processNumber, context, isLocal, callChainJSON, ...} records onto Storage.testErrors (a shared collection).

  2. In the host's test entry point, install both handlers and pass the host's Storage to the component:

    // RunTests.4dmON ERR CALL("TestErrorHandler")
    ON ERR CALL("TestGlobalErrorHandler"; 1)
    Testing_RunTestsWithCs(cs; Storage; $userParams)
  3. The component drains Storage.testErrors per-test (matched by processNumber) and globally, and surfaces them in every output format: per-test runtimeErrors[] in JSON, top-level globalErrors[], and JUnit <system-err>.

Documentation

License

MIT License

About

A 4D unit testing component

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

4D Unit Testing Framework

A comprehensive unit testing framework for the 4D platform with test tagging, filtering, and CI/CD integration.

Key Features

  • Auto test discovery - Finds test classes ending with "Test"
  • Test tagging - Organize tests with // #tags: unit, integration, slow
  • Flexible filtering - Run specific tests by name, pattern, or tags
  • Multiple output formats - Human-readable, JSON, and JUnit XML
  • CI/CD ready - Structured JSON / JUnit XML output for automated testing
  • Transaction management - Automatic test isolation with rollback
  • Subtests - Run table-driven tests with t.run
  • Host runtime-error capture - When used as a component, captures and reports host-side runtime errors (per-test and global) with stack chains

Quick Example

// UserServiceTest.4dmClass constructor()
// #tags: unit, fastFunction test_user_creation($t : cs.Testing)
 var$user : Object
$user:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.isNotNull($t; $user.name; "User should have a name")
$t.assert.areEqual($t; "John"; $user.name; "User name should be correct")
// Compare complex objects with deep equality var$expected : Object
$expected:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.areDeepEqual($t; $expected; $user; "User object should match expected structure")
// #tags: integration, databaseFunction test_user_persistence($t : cs.Testing)
// Database operations are automatically rolled back var$user : cs.UsersEntity$user:=ds.Users.new()
$user.name:="John"$user.email:="john@example.com" var$result : Object
$result:=$user.save()
$t.assert.isTrue($t; $result.success; "User should be saved successfully")

Setup

Create a project method (e.g., "test") with this code:

var$runner : cs.Testing.TestRunner$runner:=cs.Testing.TestRunner.new(cs)
$runner.run()

Running Tests

# Run all tests
tool4d --project YourProject.4DProject --startup-method "test"# Run with JSON output
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json"# Write JSON to a file (clean output, even with debug logs on stdout)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json outputPath=test-results/report.json"# Include call chains on failed tests in terse JSON without going full verbose
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json callchain=true"# JUnit XML output (CI-friendly: failures, errors, skipped, system-err)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=junit outputPath=test-results/junit.xml"# Run specific tests
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest.test_user_creation"# Filter by tags
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit excludeTags=slow"

Table-Driven Tests

Use subtests to build table-driven tests. Each call to t.run executes the provided function with a fresh testing context. If a subtest fails, the parent test is marked as failed. Subtests run with the same This object as the parent test, so helper methods and state remain accessible. Pass optional data as the third argument when the test logic lives in a separate method.

Function test_math($t : cs.Testing)
 var$cases : Collection
$cases:=[New object("name"; "1+1"; "in"; 1; "want"; 2)]
 var$case : Object
 For each ($case; $cases)
$t.run($case.name; This._checkMathCase; $case)
 End for eachFunction _checkMathCase($t : cs.Testing; $case : Object)
$t.assert.areEqual($t; $case.want; $case.in+1; "math works")

Output

Human format:

 ✓ test_user_creation (1ms)
✓ test_user_persistence (5ms)
2 tests passed

JSON format (terse — default with format=json):

{
"tests": 2,
"passed": 2,
"failed": 0,
"skipped": 0,
"duration": 6,
"rate": 100.0,
"status": "ok",
"globalErrorCount": 0,
"globalErrors": [],
"testResults": [ /* per-test entries with assertions[] and runtimeErrors[] */ ],"failures": [ /* one entry per failed test, includes callChain when verbose=true or callchain=true */ ]
}

External (non-test) runtime errors captured during the run live in globalErrors[] / globalErrorCount. JUnit output reports the same data in a <system-err> block.

Capturing Host Runtime Errors

When the testing framework is loaded as a component into a host project, host-side code (triggers, workers, methods called by tests) can raise runtime errors that the component's own ON ERR CALL cannot reach. To capture them:

  1. Define TestErrorHandler and TestGlobalErrorHandler project methods in the host that push {code, text, method, line, message, processNumber, context, isLocal, callChainJSON, ...} records onto Storage.testErrors (a shared collection).

  2. In the host's test entry point, install both handlers and pass the host's Storage to the component:

    // RunTests.4dmON ERR CALL("TestErrorHandler")
    ON ERR CALL("TestGlobalErrorHandler"; 1)
    Testing_RunTestsWithCs(cs; Storage; $userParams)
  3. The component drains Storage.testErrors per-test (matched by processNumber) and globally, and surfaces them in every output format: per-test runtimeErrors[] in JSON, top-level globalErrors[], and JUnit <system-err>.

Documentation

License

MIT License

About

A 4D unit testing component

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

4D Unit Testing Framework

A comprehensive unit testing framework for the 4D platform with test tagging, filtering, and CI/CD integration.

Key Features

  • Auto test discovery - Finds test classes ending with "Test"
  • Test tagging - Organize tests with // #tags: unit, integration, slow
  • Flexible filtering - Run specific tests by name, pattern, or tags
  • Multiple output formats - Human-readable, JSON, and JUnit XML
  • CI/CD ready - Structured JSON / JUnit XML output for automated testing
  • Transaction management - Automatic test isolation with rollback
  • Subtests - Run table-driven tests with t.run
  • Host runtime-error capture - When used as a component, captures and reports host-side runtime errors (per-test and global) with stack chains

Quick Example

// UserServiceTest.4dmClass constructor()
// #tags: unit, fastFunction test_user_creation($t : cs.Testing)
 var$user : Object
$user:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.isNotNull($t; $user.name; "User should have a name")
$t.assert.areEqual($t; "John"; $user.name; "User name should be correct")
// Compare complex objects with deep equality var$expected : Object
$expected:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.areDeepEqual($t; $expected; $user; "User object should match expected structure")
// #tags: integration, databaseFunction test_user_persistence($t : cs.Testing)
// Database operations are automatically rolled back var$user : cs.UsersEntity$user:=ds.Users.new()
$user.name:="John"$user.email:="john@example.com" var$result : Object
$result:=$user.save()
$t.assert.isTrue($t; $result.success; "User should be saved successfully")

Setup

Create a project method (e.g., "test") with this code:

var$runner : cs.Testing.TestRunner$runner:=cs.Testing.TestRunner.new(cs)
$runner.run()

Running Tests

# Run all tests
tool4d --project YourProject.4DProject --startup-method "test"# Run with JSON output
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json"# Write JSON to a file (clean output, even with debug logs on stdout)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json outputPath=test-results/report.json"# Include call chains on failed tests in terse JSON without going full verbose
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json callchain=true"# JUnit XML output (CI-friendly: failures, errors, skipped, system-err)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=junit outputPath=test-results/junit.xml"# Run specific tests
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest.test_user_creation"# Filter by tags
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit excludeTags=slow"

Table-Driven Tests

Use subtests to build table-driven tests. Each call to t.run executes the provided function with a fresh testing context. If a subtest fails, the parent test is marked as failed. Subtests run with the same This object as the parent test, so helper methods and state remain accessible. Pass optional data as the third argument when the test logic lives in a separate method.

Function test_math($t : cs.Testing)
 var$cases : Collection
$cases:=[New object("name"; "1+1"; "in"; 1; "want"; 2)]
 var$case : Object
 For each ($case; $cases)
$t.run($case.name; This._checkMathCase; $case)
 End for eachFunction _checkMathCase($t : cs.Testing; $case : Object)
$t.assert.areEqual($t; $case.want; $case.in+1; "math works")

Output

Human format:

 ✓ test_user_creation (1ms)
✓ test_user_persistence (5ms)
2 tests passed

JSON format (terse — default with format=json):

{
"tests": 2,
"passed": 2,
"failed": 0,
"skipped": 0,
"duration": 6,
"rate": 100.0,
"status": "ok",
"globalErrorCount": 0,
"globalErrors": [],
"testResults": [ /* per-test entries with assertions[] and runtimeErrors[] */ ],"failures": [ /* one entry per failed test, includes callChain when verbose=true or callchain=true */ ]
}

External (non-test) runtime errors captured during the run live in globalErrors[] / globalErrorCount. JUnit output reports the same data in a <system-err> block.

Capturing Host Runtime Errors

When the testing framework is loaded as a component into a host project, host-side code (triggers, workers, methods called by tests) can raise runtime errors that the component's own ON ERR CALL cannot reach. To capture them:

  1. Define TestErrorHandler and TestGlobalErrorHandler project methods in the host that push {code, text, method, line, message, processNumber, context, isLocal, callChainJSON, ...} records onto Storage.testErrors (a shared collection).

  2. In the host's test entry point, install both handlers and pass the host's Storage to the component:

    // RunTests.4dmON ERR CALL("TestErrorHandler")
    ON ERR CALL("TestGlobalErrorHandler"; 1)
    Testing_RunTestsWithCs(cs; Storage; $userParams)
  3. The component drains Storage.testErrors per-test (matched by processNumber) and globally, and surfaces them in every output format: per-test runtimeErrors[] in JSON, top-level globalErrors[], and JUnit <system-err>.

Documentation

License

MIT License

About

A 4D unit testing component

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

4D Unit Testing Framework

A comprehensive unit testing framework for the 4D platform with test tagging, filtering, and CI/CD integration.

Key Features

  • Auto test discovery - Finds test classes ending with "Test"
  • Test tagging - Organize tests with // #tags: unit, integration, slow
  • Flexible filtering - Run specific tests by name, pattern, or tags
  • Multiple output formats - Human-readable, JSON, and JUnit XML
  • CI/CD ready - Structured JSON / JUnit XML output for automated testing
  • Transaction management - Automatic test isolation with rollback
  • Subtests - Run table-driven tests with t.run
  • Host runtime-error capture - When used as a component, captures and reports host-side runtime errors (per-test and global) with stack chains

Quick Example

// UserServiceTest.4dmClass constructor()
// #tags: unit, fastFunction test_user_creation($t : cs.Testing)
 var$user : Object
$user:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.isNotNull($t; $user.name; "User should have a name")
$t.assert.areEqual($t; "John"; $user.name; "User name should be correct")
// Compare complex objects with deep equality var$expected : Object
$expected:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.areDeepEqual($t; $expected; $user; "User object should match expected structure")
// #tags: integration, databaseFunction test_user_persistence($t : cs.Testing)
// Database operations are automatically rolled back var$user : cs.UsersEntity$user:=ds.Users.new()
$user.name:="John"$user.email:="john@example.com" var$result : Object
$result:=$user.save()
$t.assert.isTrue($t; $result.success; "User should be saved successfully")

Setup

Create a project method (e.g., "test") with this code:

var$runner : cs.Testing.TestRunner$runner:=cs.Testing.TestRunner.new(cs)
$runner.run()

Running Tests

# Run all tests
tool4d --project YourProject.4DProject --startup-method "test"# Run with JSON output
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json"# Write JSON to a file (clean output, even with debug logs on stdout)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json outputPath=test-results/report.json"# Include call chains on failed tests in terse JSON without going full verbose
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json callchain=true"# JUnit XML output (CI-friendly: failures, errors, skipped, system-err)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=junit outputPath=test-results/junit.xml"# Run specific tests
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest.test_user_creation"# Filter by tags
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit excludeTags=slow"

Table-Driven Tests

Use subtests to build table-driven tests. Each call to t.run executes the provided function with a fresh testing context. If a subtest fails, the parent test is marked as failed. Subtests run with the same This object as the parent test, so helper methods and state remain accessible. Pass optional data as the third argument when the test logic lives in a separate method.

Function test_math($t : cs.Testing)
 var$cases : Collection
$cases:=[New object("name"; "1+1"; "in"; 1; "want"; 2)]
 var$case : Object
 For each ($case; $cases)
$t.run($case.name; This._checkMathCase; $case)
 End for eachFunction _checkMathCase($t : cs.Testing; $case : Object)
$t.assert.areEqual($t; $case.want; $case.in+1; "math works")

Output

Human format:

 ✓ test_user_creation (1ms)
✓ test_user_persistence (5ms)
2 tests passed

JSON format (terse — default with format=json):

{
"tests": 2,
"passed": 2,
"failed": 0,
"skipped": 0,
"duration": 6,
"rate": 100.0,
"status": "ok",
"globalErrorCount": 0,
"globalErrors": [],
"testResults": [ /* per-test entries with assertions[] and runtimeErrors[] */ ],"failures": [ /* one entry per failed test, includes callChain when verbose=true or callchain=true */ ]
}

External (non-test) runtime errors captured during the run live in globalErrors[] / globalErrorCount. JUnit output reports the same data in a <system-err> block.

Capturing Host Runtime Errors

When the testing framework is loaded as a component into a host project, host-side code (triggers, workers, methods called by tests) can raise runtime errors that the component's own ON ERR CALL cannot reach. To capture them:

  1. Define TestErrorHandler and TestGlobalErrorHandler project methods in the host that push {code, text, method, line, message, processNumber, context, isLocal, callChainJSON, ...} records onto Storage.testErrors (a shared collection).

  2. In the host's test entry point, install both handlers and pass the host's Storage to the component:

    // RunTests.4dmON ERR CALL("TestErrorHandler")
    ON ERR CALL("TestGlobalErrorHandler"; 1)
    Testing_RunTestsWithCs(cs; Storage; $userParams)
  3. The component drains Storage.testErrors per-test (matched by processNumber) and globally, and surfaces them in every output format: per-test runtimeErrors[] in JSON, top-level globalErrors[], and JUnit <system-err>.

Documentation

License

MIT License

About

A 4D unit testing component

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

4D Unit Testing Framework

A comprehensive unit testing framework for the 4D platform with test tagging, filtering, and CI/CD integration.

Key Features

  • Auto test discovery - Finds test classes ending with "Test"
  • Test tagging - Organize tests with // #tags: unit, integration, slow
  • Flexible filtering - Run specific tests by name, pattern, or tags
  • Multiple output formats - Human-readable, JSON, and JUnit XML
  • CI/CD ready - Structured JSON / JUnit XML output for automated testing
  • Transaction management - Automatic test isolation with rollback
  • Subtests - Run table-driven tests with t.run
  • Host runtime-error capture - When used as a component, captures and reports host-side runtime errors (per-test and global) with stack chains

Quick Example

// UserServiceTest.4dmClass constructor()
// #tags: unit, fastFunction test_user_creation($t : cs.Testing)
 var$user : Object
$user:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.isNotNull($t; $user.name; "User should have a name")
$t.assert.areEqual($t; "John"; $user.name; "User name should be correct")
// Compare complex objects with deep equality var$expected : Object
$expected:=New object("name"; "John"; "email"; "john@example.com")
$t.assert.areDeepEqual($t; $expected; $user; "User object should match expected structure")
// #tags: integration, databaseFunction test_user_persistence($t : cs.Testing)
// Database operations are automatically rolled back var$user : cs.UsersEntity$user:=ds.Users.new()
$user.name:="John"$user.email:="john@example.com" var$result : Object
$result:=$user.save()
$t.assert.isTrue($t; $result.success; "User should be saved successfully")

Setup

Create a project method (e.g., "test") with this code:

var$runner : cs.Testing.TestRunner$runner:=cs.Testing.TestRunner.new(cs)
$runner.run()

Running Tests

# Run all tests
tool4d --project YourProject.4DProject --startup-method "test"# Run with JSON output
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json"# Write JSON to a file (clean output, even with debug logs on stdout)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json outputPath=test-results/report.json"# Include call chains on failed tests in terse JSON without going full verbose
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=json callchain=true"# JUnit XML output (CI-friendly: failures, errors, skipped, system-err)
tool4d --project YourProject.4DProject --startup-method "test" --user-param "format=junit outputPath=test-results/junit.xml"# Run specific tests
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "test=UserServiceTest.test_user_creation"# Filter by tags
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit"
tool4d --project YourProject.4DProject --startup-method "test" --user-param "tags=unit excludeTags=slow"

Table-Driven Tests

Use subtests to build table-driven tests. Each call to t.run executes the provided function with a fresh testing context. If a subtest fails, the parent test is marked as failed. Subtests run with the same This object as the parent test, so helper methods and state remain accessible. Pass optional data as the third argument when the test logic lives in a separate method.

Function test_math($t : cs.Testing)
 var$cases : Collection
$cases:=[New object("name"; "1+1"; "in"; 1; "want"; 2)]
 var$case : Object
 For each ($case; $cases)
$t.run($case.name; This._checkMathCase; $case)
 End for eachFunction _checkMathCase($t : cs.Testing; $case : Object)
$t.assert.areEqual($t; $case.want; $case.in+1; "math works")

Output

Human format:

 ✓ test_user_creation (1ms)
✓ test_user_persistence (5ms)
2 tests passed

JSON format (terse — default with format=json):

{
"tests": 2,
"passed": 2,
"failed": 0,
"skipped": 0,
"duration": 6,
"rate": 100.0,
"status": "ok",
"globalErrorCount": 0,
"globalErrors": [],
"testResults": [ /* per-test entries with assertions[] and runtimeErrors[] */ ],"failures": [ /* one entry per failed test, includes callChain when verbose=true or callchain=true */ ]
}

External (non-test) runtime errors captured during the run live in globalErrors[] / globalErrorCount. JUnit output reports the same data in a <system-err> block.

Capturing Host Runtime Errors

When the testing framework is loaded as a component into a host project, host-side code (triggers, workers, methods called by tests) can raise runtime errors that the component's own ON ERR CALL cannot reach. To capture them:

  1. Define TestErrorHandler and TestGlobalErrorHandler project methods in the host that push {code, text, method, line, message, processNumber, context, isLocal, callChainJSON, ...} records onto Storage.testErrors (a shared collection).

  2. In the host's test entry point, install both handlers and pass the host's Storage to the component:

    // RunTests.4dmON ERR CALL("TestErrorHandler")
    ON ERR CALL("TestGlobalErrorHandler"; 1)
    Testing_RunTestsWithCs(cs; Storage; $userParams)
  3. The component drains Storage.testErrors per-test (matched by processNumber) and globally, and surfaces them in every output format: per-test runtimeErrors[] in JSON, top-level globalErrors[], and JUnit <system-err>.

Documentation

License

MIT License

About

A 4D unit testing component

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages