Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 119 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

v2.x

Loading...

Introduction

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Primers

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

In-Depth

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Mocking

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

What's New With 2.6.0

TestBox 2.6.0 is a minor release with some great new functionality and tons of fixes. You can find the release notes here and the major updates for this release.

Chained DSL Improvements

The methods aroundEach(), beforeEach() and afterEach() can now be chained as they return the Base Spec as well. So get funky with the DSL.

Null Support

You can now pass null into an expectation and TestBox will gracefully represent it instead of blowing up!

Method Mocking Performance

Eager Failures

We have added a new argument to all TestBox run(), runRemote(), runRaw() commands: eagerFailure, which defaults to false. If this is turned on, then TestBox will gracefully short-circuit out of testing further Test Bundles if it finds any failures or errors on previous ones. This is useful for large suites that you want to stop testing if a failure is discovered.

Help for the Color Blind

Before

After

Expanding of x exclusion Methods

You can now prefix the following methods with the letter x to exclude them from execution:

  • story()

  • given()

  • when()

  • then()

  • feature()

Release Notes

Bugs

New Features

Improvements

Introduction

TestBox & MockBox Manual v2.x

Features At A Glance

Here are a simple listing of features TestBox brings to the table:

  • BDD style testing

  • xUnit style testing

  • Testing life-cycle methods

  • Ability to extend and create custom test runners

  • Ability to extend and create custom test reporters

  • Extensible reporters, bundled with tons of them:

    • JSON

    • XML

    • JUnit 4 XML

    • Text (80's style)

    • Console

    • Simple HTML

    • Min - Minimalistic Heaven

    • Raw

    • CommandBox

  • Asynchronous testing

  • Multi-suite capabilities

  • Test skipping

  • Suite skipping

  • Dynamic skipping support via runtime executions

  • Test one or more suites exclusively

  • Test one or more tests/specs exclusively

  • Test labels and tagging

  • Testing debug output stream

  • Clickable suite titles to filter test execution

  • Much more!

Versioning

<major>.<minor>.<patch>

And constructed with the following guidelines:

  • Breaking backward compatibility bumps the major (and resets the minor and patch)

  • New additions without breaking backward compatibility bumps the minor (and resets the patch)

  • Bug fixes and misc changes bumps the patch

License

  • Copyright by Ortus Solutions, Corp

  • TestBox is a registered trademark by Ortus Solutions, Corp

Info: The ColdBox Websites, Documentation, logo and content have a separate license and they are a separate entity.

Discussion & Help

Reporting a Bug

Professional Open Source

  • Custom Development

  • Professional Support & Mentoring

  • Training

  • Server Tuning

  • Security Hardening

  • Code Reviews

Resources

HONOR GOES TO GOD ABOVE ALL

Because of His grace, this project exists. If you don't like this, then don't read it, its not for you.

Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. - Romans 5:5

What's New With 2.7.0

TestBox 2.7.0 is a minor release with some great new functionality and tons of fixes. You can find the release notes here and the major updates for this release.

Release Notes

Bugs

New Features

Improvements

Introduction

TestBox & MockBox Manual v2.x

Features At A Glance

Here are a simple listing of features TestBox brings to the table:

  • BDD style testing

  • xUnit style testing

  • Testing life-cycle methods

  • Ability to extend and create custom test runners

  • Ability to extend and create custom test reporters

  • Extensible reporters, bundled with tons of them:

    • JSON

    • XML

    • JUnit 4 XML

    • Text (80's style)

    • Console

    • Simple HTML

    • Min - Minimalistic Heaven

    • Raw

    • CommandBox

  • Asynchronous testing

  • Multi-suite capabilities

  • Test skipping

  • Suite skipping

  • Dynamic skipping support via runtime executions

  • Test one or more suites exclusively

  • Test one or more tests/specs exclusively

  • Test labels and tagging

  • Testing debug output stream

  • Clickable suite titles to filter test execution

  • Much more!

Versioning

<major>.<minor>.<patch>

And constructed with the following guidelines:

  • Breaking backward compatibility bumps the major (and resets the minor and patch)

  • New additions without breaking backward compatibility bumps the minor (and resets the patch)

  • Bug fixes and misc changes bumps the patch

License

  • Copyright by Ortus Solutions, Corp

  • TestBox is a registered trademark by Ortus Solutions, Corp

Info: The ColdBox Websites, Documentation, logo and content have a separate license and they are a separate entity.

Discussion & Help

Reporting a Bug

Professional Open Source

  • Custom Development

  • Professional Support & Mentoring

  • Training

  • Server Tuning

  • Security Hardening

  • Code Reviews

Resources

HONOR GOES TO GOD ABOVE ALL

Because of His grace, this project exists. If you don't like this, then don't read it, its not for you.

Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. - Romans 5:5

What's New With 2.8.0

TestBox 2.8.0 is a minor release with some great new functionality and tons of fixes. You can find the release notes here and the major updates for this release.

Release Notes

Bugs

New Features

Improvements

What's New With 2.5.0

TestBox 2.5.0 is a minor release with some great new functionality and tons of fixes. You can find the release notes here and the major updates for this release. One of the biggest features for TestBox that was not part of TestBox, was the addition of TestBox Watchers to CommandBox.

TestBox Watchers

CommandBox CLI has added watching capabilities to the testbox namespace. Which means you can now run the following command in the root of your project: testbox watch and it will monitor all your CFCs for changes. If a change is detected, then it will fire the new testbox run command with our CLI reporter.

Life-Cycle Data Binding

We had introduced spec data binding in TestBox for creating dynamic specs and passing dynamic data into them. We have extended this feature into life-cycle methods:

  • beforeEach()

  • afterEach()

  • aroundEach()

You can now pass in an argument called data which is a struct of dynamic data to pass into the life-cycle method. You can then pickup this data in the closure for the life-cycle. Here is a typical example:

describe( "Ability to bind data to life-cycle methods", function(){

    var data = [
        "spec1",
        "spec2"
    ];

    for( var thisData in data ){
        describe( "Trying #thisData#", function(){

            beforeEach( data={ myData = thisData }, body=function( currentSpec, data ){
                targetData = arguments.data.myData;
            });

            it( title="should account for life-cycle data binding", 
                data={ myData = thisData},
                body=function( data ){
                    expect(    targetData ).toBe( data.mydata );
                }
            );

            afterEach( data={ myData = thisData }, body=function( currentSpec, data ){
                targetData = arguments.data.myData;
            });
        });
    }

    for( var thisData in data ){

        describe( "Trying around life-cycles with #thisData#", function(){

            aroundEach( data={ myData = thisData }, body = function( spec, suite, data ){
                targetData = arguments.data.myData;
                arguments.spec.body( data=arguments.spec.data );
            });

            it( title="should account for life-cycle data binding", 
                data={ myData = thisData },
                body=function( data ){
                    expect(    targetData ).toBe( data.mydata );
                }
            );

        });

    }
});

Release Notes

Bugs

Improvements

Thanks to method mocking performance has sky rocketed. He changed the filename generation to an MD5 hash of the code, and disabled removing the files. This allows TestBox to leverage ColdFusion template caching. This reduces the time that $() takes from ~30 ms to ~0 ms. It is a happy day for mocking!

This was another community driven contribution by to help those folks with . You can see the differences in color in the github pull request:

[] - Assertion argument order does not match testbox.system.assertion for the expected argument

[] - MockBox method subs are leaking whitespace

[] - Labels in an it() are ignored

[] - The recurse checkbox in test-runner doesn't work

[] - TestBox doesn't handle an interface CFC in the specs folder and chokes

[] - Return this from around,beforeEach and afterEach to have a chained DSL thanks to Jose Chavez

[] - Output null representation in output message

[] - wrap bundle runner execution in try/catch with rethrow

[] - Improved method mocking performance

[] - Provide a way to mute debug buffer on marshalled results

[] - Add eager failure argument to runner so if set and a bundle fails no more bundles are tested after

[] - Use correct key casing in report output

[] - switch pass/failed/error colors to help color blindness

[] - Exclude empty test suites from ANT JUnit Reporting

[] - Add x- methods to skip suites and specs to new BDD methods.

[] - Don't re-mock already mocked objects. #63

TestBox is a next generation testing framework for ColdFusion (CFML) that is based on (Behavior Driven Development) for providing a clean obvious syntax for writing tests. It contains not only a testing framework, runner, assertions and expectations library but also ships with MockBox, A mocking and stubbing companion. It also supports xUnit style of testing and MXUnit compatibilities.

integration for mocking and stubbing

TAP ()

TestBox is maintained under the guidelines as much as possible.Releases will be numbered with the following format:

TestBox and MockBox are open source and licensed under the License. If you use them please try to make mention of it in your code or web site.

Help Group :

BoxTeam Slack :

CFML Slack: Look for the box-products channel:

We all make mistakes from time to time :) So why not let us know about it and help us out. We also love pull requests, so please star us and fork us:

By Jira:

TestBox is a professional open source software backed by offering services like:

Official Site:

Current API Docs:

Help Group:

Source Code:

Bug Tracker :

Twitter:

Facebook:

[] - new version of Lucee has complex values in UDF implementation that breaks the mock generator

[] - Heap issues and stack overflow issues when normalizing ORM entities in mocking arguments

[] - Complete refactoring of around,before,after each executions to support concurrency

[] - some refactoring to use invoke() instead of evaluate()

[] - some speed improvements by not using createuuid anymore

TestBox is a next generation testing framework for ColdFusion (CFML) that is based on (Behavior Driven Development) for providing a clean obvious syntax for writing tests. It contains not only a testing framework, runner, assertions and expectations library but also ships with MockBox, A mocking and stubbing companion. It also supports xUnit style of testing and MXUnit compatibilities.

integration for mocking and stubbing

TAP ()

TestBox is maintained under the guidelines as much as possible.Releases will be numbered with the following format:

TestBox and MockBox are open source and licensed under the License. If you use them please try to make mention of it in your code or web site.

Help Group :

BoxTeam Slack :

CFML Slack: Look for the box-products channel:

We all make mistakes from time to time :) So why not let us know about it and help us out. We also love pull requests, so please star us and fork us:

By Jira:

TestBox is a professional open source software backed by offering services like:

Official Site:

Current API Docs:

Help Group:

Source Code:

Bug Tracker :

Twitter:

Facebook:

[] - Bug on SimpleReporter name not complete

[] - Recurse parameter not honored #71 on html runner

[] - Junit reporter blows up on null values in server scope

[] - Update Expectation.cfc to make toMatchWithCase consistent as it is not working

[] - Add ability to exclude labels from running using `url.excludes`

[] - Incompatibilities with Adobe on many isValid and isInstanceOf

[] - Simple reporter broken on Adobe servers

[] - HTML Runner links should include directory param

[] - AsyncAll Errroring on xUnit runner

[] - the 'type' argument was being ignored if the 'regex' argument was provided and matched the exception data

[] - Remove thread scope from debug stream

[] - Passing Data to Dynamic Tests via life-cycle methods: around, after, before

[] - Preserve whitespace in HTML (simple) Reporter

[] - Include TestBox version in TestResult memento

[] - Let travis handle upload of api docs and snapshot binaries

[] - Aggregate suite stats from nested suties

Pixilation
Ian Burns - iwburns
Color Blindness
https://github.com/Ortus-Solutions/TestBox/pull/60
TESTBOX-196
TESTBOX-199
TESTBOX-203
TESTBOX-215
TESTBOX-216
TESTBOX-195
TESTBOX-200
TESTBOX-201
TESTBOX-202
TESTBOX-204
TESTBOX-205
TESTBOX-208
TESTBOX-210
TESTBOX-211
TESTBOX-212
TESTBOX-214
BDD
MockBox
Test Anything Protocol
Semantic Versioning
Apache 2
https://groups.google.com/a/ortussolutions.com/forum/#!forum/testbox
https://boxteam.herokuapp.com
http://cfml.slack.com
https://github.com/Ortus-Solutions/TestBox
https://ortussolutions.atlassian.net/browse/TESTBOX
Ortus Solutions, Corp
Much More
https://www.ortussolutions.com/products/testbox
http://apidocs.ortussolutions.com/testbox/current
https://groups.google.com/a/ortussolutions.com/forum/#!forum/testbox
https://github.com/Ortus-Solutions/TestBox
https://ortussolutions.atlassian.net/browse/TESTBOX
@ortussolutions
https://www.facebook.com/ortussolutions
TESTBOX-217
TESTBOX-222
TESTBOX-221
TESTBOX-219
TESTBOX-220
BDD
MockBox
Test Anything Protocol
Semantic Versioning
Apache 2
https://groups.google.com/a/ortussolutions.com/forum/#!forum/testbox
https://boxteam.herokuapp.com
http://cfml.slack.com
https://github.com/Ortus-Solutions/TestBox
https://ortussolutions.atlassian.net/browse/TESTBOX
Ortus Solutions, Corp
Much More
https://www.ortussolutions.com/products/testbox
http://apidocs.ortussolutions.com/testbox/current
https://groups.google.com/a/ortussolutions.com/forum/#!forum/testbox
https://github.com/Ortus-Solutions/TestBox
https://ortussolutions.atlassian.net/browse/TESTBOX
@ortussolutions
https://www.facebook.com/ortussolutions
TESTBOX-224
TESTBOX-231
TESTBOX-232
TESTBOX-233
TESTBOX-225
TESTBOX-230
TESTBOX-177
TESTBOX-180
TESTBOX-181
TESTBOX-183
TESTBOX-178
TESTBOX-184
TESTBOX-185
TESTBOX-186
TESTBOX-187
TESTBOX-188

About This Book

  • The majority of code examples in this book are done in cfscript.

  • All ColdFusion examples designed to run on the open soure Railo Platform or Adobe ColdFusion 9.0.2+

External Trademarks & Copyrights

Flash, Flex, ColdFusion, and Adobe are registered trademarks and copyrights of Adobe Systems, Inc.

Notice of Liability

The information in this book is distributed “as is”, without warranty. The author and Ortus Solutions, Corp shall not have any liability to any person or entity with respect to loss or damage caused or alleged to be caused directly or indirectly by the content of this training book, software and resources described in it.

Contributing

Charitable Proceeds

Shalom Children's Home

Shalom now cares for over 80 children in El Salvador, from newborns to 18 years old. They receive shelter, clothing, food, medical care, education and life skills training in a Christian environment. The home is supported by a child sponsorship program.

We have personally supported Shalom for over 6 years now; it is a place of blessing for many children in El Salvador that either have no families or have been abandoned. This is good earth to seed and plant.

What's New With 2.2.0

TestBox 2.2.0 is a minor release with some great new functionality and tons of fixes.

Release Notes

Bugs

New Features

Improvements

Author

Luis Fernando Majano Lainez

Luis has a passion for Jesus, tennis, golf, volleyball and anything electronic. Random Author Facts:

  • He played volleyball in the Salvadorean National Team at the tender age of 17

  • The Lord of the Rings and The Hobbit is something he reads every 5 years. (Geek!)

  • His first ever computer was a Texas Instrument TI-86 that his parents gave him in 1986. After some time digesting his very first BASIC book, he had written his own tic-tac-toe game at the age of 9. (Extra geek!)

  • He has a geek love for circuits, microcontrollers and overall embedded systems.

  • He has of late (during old age) become a fan of running and bike riding with his family.

Keep Jesus number one in your life and in your heart. I did and it changed my life from desolation, defeat and failure to an abundant life full of love, thankfulness, joy and overwhelming peace. As this world breathes failure and fear upon any life, Jesus brings power, love and a sound mind to everybody!

“Trust in the LORD with all your heart, and do not lean on your own understanding.” Proverbs 3:5

Contributors

Jorge Emilio Reyes Bendeck

Jorge started working as project manager and business developer at Ortus Solutions, Corp. in 2013, . At Ortus he fell in love with software development and now enjoys taking part on software development projects and software documentation! He is a fellow Cristian who loves to play the guitar, worship and rejoice in the Lord!

Therefore, if anyone is in Christ, the new creation has come: The old has gone, the new is here! 2 Corinthians 5:17

What's New With 2.1.0

TestBox 2.1.0 is a minor release with some great new functionality and tons of fixes.

Release Notes

Bugs Fixed

Improvement

New Feature

Global Enhancements

  • Better whitespace management on text enabled reporting

  • ColdFusion 11 compatibilities

  • ColdFusion 9 support on xUnit and Assertions

  • HTML Runners now have a high request timeout for long-lasting tests

Debugging Enhancements

TestBox's debug() method has been enhanced to provide greater messages and telemetry. You can even control the depth of the dumps of each of the debugged data as it is sent. Here is the new signature:

The output will now include information as to what spec produced the output, timestamps, data, and thread data.

Expectation Chaining

You can now chain your matchers on a specific expectation to produce an even nicer DSL when trying to assert data on a single expectation:

AroundEach() Life-Cycle Method

We have added a new life-cycle method to the suites called aroundEach() which will completely wrap your spec in another closure. This is an elegant way for you to provide a complete around AOP advice to a specificiation. You can use it to surround the execution in transaction blocks, ORM rollbacks, logging, and so much more. This life-cycle method will decorate ALL specificiations within a single suite. The method signature is below:

The method receives a structure of data representing the spec. This contains the following elements:

  • body : The actual closure for the spec that you will use to execute within it.

  • labels : The labels used in the spec

  • name : The name of the spec

  • order : The order of execution of the spec

  • skip : The skip flag or closure that determines if the spec runs

The method also receives a structure of metadata about the suite this spec is contained in. It has information about life-cycle closures, async information, names, parents, etc. Here is a very practical example of creating and around each closure to provide rollbacks for specs:

This simple around each life-cycle closure will rollback ALL my spec's executions even if they throw exceptions.

Simple Reporter Bundle Filter

The simple reporter has been enhanced to add a bundle test filter that can help reduce noise when looking for a specific bundle result:

run() Enhancements

The traditional BDD run() has been enhanced so it now receives the TestBox TestResults object as a reference and TestBox as a reference. This way you can have more metadata and access to what will be reported to users in a reporter. You can also use it to decorate it or store much more information that can be picked up later by reports. You also have access to the TestBox class so you can see how the test is supposed to execute, what labels was it passed, directories, options, etc.

RefCards

Our RefCards will get you up and running in now time.

BDD RefCard

xUnit RefCard

What's New With 2.4.0

TestBox 2.4.0 is a minor release with some great new functionality and tons of fixes. This release has been a great community effort as many people in the community contributed to its release. Special thanks to Eric Peterson, Joe Gooch and Sean Corfield for their additions, testing and contributions.

New toSatisfy matcher

This new matcher is thanks to Sean Corfield. It allows you to create your own closure that will evaluate the expectation and then decide if it passes the given truth test.

New expectAll() collection expectation

Sean was busy in this release and provided us with this awesome feature in which you can call on a new expectAll() and pass either an array or struct. TestBox will then iterate for you and call all the chained matchers upon the collection items.

New mintext Reporter

This new reporter is to enhance console based runners in order for the report to be more legible.

New JSON Matchers & Assertions

You can now use a toBeJSON() matcher or a $assert.isJSON assertion.

No more runRemote

You no longer need to pass ?method=runRemote in the URL when executing a test bundle via the URL. This will automatically be added for you.

New Fluent API for Testing Declarations

Thanks to Joe Gooch you can now use the new methods in the TestBox cfc

  • addDirectory()

  • addDirectories()

  • addBundles()

You can chain them as you see fit and they will aggregate the specs collected.

Release Notes

Here is the full release notes for this release

Bugs

New Features

Improvements

The source code for this book is hosted in GitHub: . You can freely contribute to it and submit pull requests. The contents of this book is copyright by and cannot be altered or reproduced without author's consent. All content is provided "As-Is" and can be freely distributed.

The majority of code generation and running of examples are done via CommandBox: The ColdFusion (CFML) CLI, Package Manager, REPL -

We highly encourage contribution to this book and our open source software. The source code for this book can be found in our where you can submit pull requests.

15% of the proceeds of this book will go to charity to support orphaned kids in El Salvador - . So please donate and purchase the printed version of this book, every book sold can help a child for almost 2 months.

Shalom Children’s Home () is one of the ministries that is dear to our hearts located in El Salvador. During the 12 year civil war that ended in 1990, many children were left orphaned or abandoned by parents who fled El Salvador. The Benners saw the need to help these children and received 13 children in 1982. Little by little, more children werecame on their own, churches and the government brought children to them for care, and the Shalom Children’s Home was founded.

[] - Syntax error in docs for Assertion Registration

[] - Docs for Assertion CFCs inadequate, or functionality just doesn't work?

[] - MXUnit injectMethod not working if calls to methods being replaced are not "this" scoped

[] - Order of args for init function did not match the interface

[] - Mockbox: object instances as args do not always pass comparison

[] - throwErrorCode addition to mock method generator

[] - Ability to bind data to a spec

[] - Support story-given-when-then style of representing tests

[] - MockBox no longer standalone package

[] - run method listeners so you can do callbacks on testing progress

[] - Ability to read assets from current directory to support lucee

[] - Support Lucee's localMode="modern"

[] - it() callback should not need to be a closure

[] - Mocking method CallBack argument receives all arguments now.

[] - Change toIncludeWithCase arguments to match toInclude

Luis Majano is a Computer Engineer with over 16 years of software development and systems architecture experience. He was born in in the late 70’s, during a period of economical instability and civil war. He lived in El Salvador until 1995 and then moved to Miami, Florida where he completed his Bachelors of Science in Computer Engineering at . Luis resides in The Woodlands, Texas with his beautiful wife Veronica, baby girl Alexia and baby boy Lucas!

He is the CEO of , a consulting firm specializing in web development, ColdFusion (CFML), Java development and all open source professional services under the ColdBox and ContentBox stack. He is the creator of ColdBox, ContentBox, WireBox, MockBox, LogBox and anything “BOX”, and contributes to many open source ColdFusion projects. He is also the Adobe ColdFusion user group manager for the . You can read his blog at

Jorge is an Industrial and Systems Engineer born in El Salvador. After finishing his Bachelor studies at the Monterrey Institute of Technology and Higher Education , Mexico, he went back to his home country where he worked as the COO of. In 2012 he left El Salvador and moved to Switzerland in persuit of the love of his life. He married her and today he resides in Basel with his lovely wife Marta and their daughter Sofía.

[] - isEqual on Query fails when queries are equal

[] - equalize fails on struct/objects/arrays when null values exist within them

[] - Floating Point Number isEqual Fails

[] - Specs with the same name cause thread name exceptions when using async

[] - Download file has "samples" instead of "tests" directory

[] - tobe() cannot handle sparse arrays on Adobe CF

[] - xUnit compatibility CF9 broken due to isClosure() being utilized

[] - skip closures get more metadata arguments when being executed.

[] - testbox errors when using complete null support in railo

[] - Have debug() include information about where it came from

[] - remove extra whitespace in text reporter

[] - Remove CF7,8 incompatibilities

[] - ColdFusion 11 cfinclude compatibilities

[] - BDD run() method now receive the TestResults argument for usage in their definitions.

[] - BDD runner and specs receive reference to the TestBox calling class via the run() method

[] - Update the apidocs with our new DocBox skin

[] - Debug labels and telemetry additions

[] - Add "top" attribute to debug method

[] - HTMLRunner add big request timeout setting to avoid server cut offs

[] - have expectations assertions return the expectation to allow chaining

[] - Simple reporter includes now a test bundle filter

[] - New lifecycle method: aroundEach() so you can do a full AOP advice on any spec

New searchable API Docs with new skin:

[] - discover if fail origin exists in errors and failures, else ignore as it causes issues

[] - Custom reporter passed as CFC instance doesn't work

[] - New mintext reporter

[] - new matcher toBeJSON and new assertion isJSON

[] - No need to pass method=runRemote anymore on spec runners, defaults now

[] - Implements fluent API - addDirectory,addBundles,addDirectories on TestBox Core

[] - runRemote operations are not setting the default response to HTML, so wddx takes over

[] - Add toSatisfy( predicate ) matcher

[] - Add expectAll() to make it easier to work with collections

https://github.com/Ortus-Solutions/testbox-docs
Ortus Solutions, Corp
http://www.ortussolutions.com/products/commandbox
GitHub repository
http://www.harvesting.org/
http://www.harvesting.org/
TESTBOX-48
TESTBOX-49
TESTBOX-120
TESTBOX-122
TESTBOX-134
TESTBOX-129
TESTBOX-132
TESTBOX-133
TESTBOX-136
TESTBOX-138
TESTBOX-121
TESTBOX-124
TESTBOX-126
TESTBOX-130
TESTBOX-135
San Salvador, El Salvador
Florida International University
Ortus Solutions
Inland Empire
www.luismajano.com
ITESM
Industrias Bendek S.A.
/**
* Debug some information into the TestBox debugger array buffer
* @var The data to debug
* @label The label to add to the debug entry
* @deepCopy By default we do not duplicate the incoming information, but you can :)
* @top The top numeric number to dump on the screen in the report, defaults to 999
*/
any function debug(
    any var,
    string label="",
    boolean deepCopy=false,
    numeric top="999"
)
// more than 1 matcher tests
it( "can have more than one expectation test", function(){
    // Can concatenate multiple matchers on a single expectation.
    expect( coldbox )
        .toBeTypeOf( 'numeric' );
        .toBeNumeric();
        .toBeCloseTo( expected=10, delta=2 );
        .notToBe( 4 );
});
aroundEach( function( spec, suite ){
    // execute the spec
    arguments.spec.body();
} );
aroundEach( function( spec, suite ){

    transaction action="begin"{
        try{/
            // execute the spec
            arguments.spec.body();
        } catch( Any e ){
            rethrow;
        } finally{
            transaction action="rollback";
        }
    }
} );
/**
* My BDD Test
*/
component extends="testbox.system.BaseSpec"{

    function run( testResults, testBox ){
        // all your suites go here.
        describe( "My First Suite", function(){

        });
    }

}
it( "can satisfy truth tests", function(){

    expect( 1 ).toSatisfy( function( num ){ return arguments.num > 0; } );

    expect( 0 ).notToSatisfy( function( num ){ return arguments.num > 0; } );

});
it( "can test a collection", function(){
    expectAll( [2,4,6,8] ).toSatisfy( function(x){ return 0 == x%2; });
    expectAll( {a:2,b:4,c:6} ).toSatisfy( function(x){ return 0 == x%2; });

    // and we can chain matchers
    expectAll( [2,4,6,8] )
        .toBeGTE( 2 )
        .toBeLTE( 8 );
});
TESTBOX-96
TESTBOX-97
TESTBOX-98
TESTBOX-100
TESTBOX-101
TESTBOX-102
TESTBOX-103
TESTBOX-105
TESTBOX-115
TESTBOX-40
TESTBOX-95
TESTBOX-110
TESTBOX-118
TESTBOX-106
TESTBOX-107
TESTBOX-108
TESTBOX-109
TESTBOX-112
TESTBOX-114
TESTBOX-116
TESTBOX-117
TESTBOX-119
http://apidocs.ortussolutions.com/testbox/2.1.0/index.html
TESTBOX-169
TESTBOX-170
TESTBOX-171
TESTBOX-172
TESTBOX-175
TESTBOX-176
TESTBOX-168
TESTBOX-173
TESTBOX-174

TestBox BDD Primer

TestBox is a xUnit style and behavior-driven development (BDD) testing framework for CFML (ColdFusion). It does not depend on any other framework for operation and it has a clean and descriptive syntax that will make you giggle when writing your tests. It ships with not only the xUnit and BDD testing capabilities, but also an assertions and expectations library, several different runners, reporters and MockBox, for mocking and stubbing capabilities.

Overview

TestBox is a next generation testing framework for ColdFusion (CFML) that is based on BDD (Behavior Driven Development) for providing a clean obvious syntax for writing tests. It contains not only a testing framework, runner, assertions, mocking/stubbing and expectations library. It also supports xUnit style of testing with MXUnit compatibilities.

Important TestBox is a standalone testing package and is meant to be used with any ColdFusion (CFML) application, framework or library. IT IS NOT ONLY FOR COLDBOX APPLICATIONS.

Useful Resources

IDE Tools

TestBox has the following supported IDE Tools:

What's New With 2.3.0

TestBox 2.3.0 is a minor release with some great new functionality and tons of fixes. This release has been a great community effort as many people in the community contributed to its release.

Release Notes

Bug

New Feature

Improvement

Installing TestBox

Note Please note the --saveDev flag which tells CommandBox that TestBox is a development dependency and not a production dependency.

This will install TestBox in a /testbox folder from where you called the command. You can also extract the download zip and place it anywhere you like and create a mapping called /testbox that points to testbox in the distribution folder, this is the most secure approach. However, you can just place it in the webroot if you like.

What's Included

The download structure includes:

  • apidocs : The API docs for TestBox

  • system : The main system framework folder

  • test-browser : This is a little utility to facilitate navigating big testing suites. This helps navigate to the suites you want and execute them instead of typing all the time.

  • test-harness : A simplified version of the TestBox runner that can be placed anywhere in your application. It includes an ANT build that allows you to execute your tests and produce results via ANT and also JUnit compliant reports via the junitreport task.

  • test-runner : The pre-built TestBox Global Runner. You can pass in a directory mapping or bundles and select labels and boom run the tests.

  • tests : Several sample tests and runners which actually are used to build TestBox

The only required folder is system and it does not need to be web-accessible. The other folders are all optional tools to help you that expect to be web-accessible. You can safely remove everything except the system folder if you want to build your own test browsers. If you are placing TestBox outside of your web root, the /testbox mapping needs to point to the parent folder containing the system directory as all TestBox models start with testbox.system in their package path.

Sublime -

VSCode -

CFBuilder -

[] - If test spec descriptor contains a comma, it can not be drilled down to run that one spec directly

[] - Allow Mocking of an Interface that implements another interface

[] - Give line number when an expectation fails or errors out

[] - new expressive exception throwing goodness: $throws()

[] - Recursively call parent `aroundEach` functions in reverse tree format

[] - Add annotation hooks for lifecycle methods

[] - remove the TestBox tag contexts from the beginning of Failure Origins

[] - Make test harness easier for development via CommandBox

[] - Add travis build support for supporting pull requests and test matrixes

[] - Update API Docs to leverage DocBox instead

[] - Explicitly place the instance "scope" in the variables scope due to lucee full cascade support

[] - update string buffers to string builders

TestBox can be downloaded from or can be installed via . CommandBox is our preferred approach for all package installations, updates and more.

You can also clone from and help us out :)

Approaches to Mocking
Wikipedia Mock Objects
Using mock objects for complex unit tests IBM developerWorks
Unit testing with mock objects IBM developerWorks
Emergent Design by Scott Bain
Mocks Aren't Stubs by Martin Fowler
https://packagecontrol.io/packages/ColdBox Platform
https://marketplace.visualstudio.com/items?itemName=ortus-solutions.vscode-testbox
https://www.forgebox.io/view/ColdBox-Platform-Utilities
TESTBOX-123
TESTBOX-140
TESTBOX-158
TESTBOX-150
TESTBOX-161
TESTBOX-162
TESTBOX-163
TESTBOX-164
TESTBOX-165
TESTBOX-166
TESTBOX-160
TESTBOX-167
// latest stable version
box install testbox --saveDev

// latest bleeding edge
box install testbox@be --saveDev
this.mappings[ "/testbox" ] = expandPath( "C:/frameworks/testbox/" );
git clone git://github.com/ortus-solutions/testbox testbox

System Requirements

If you are going to be using the xUnit style of testing which includes MXUnit compatibility, then you don't need the latest CFML engines. BDD uses closures to declare your specs, so it it is only available on newer versions of your CFML engine.

  • ColdFusion 10+

  • Lucee 4.5+

Suites: Describe Your Tests

A test suite begins with a call to our TestBox describe() function with at least two arguments: a title and a body function/closure. The title is the name of the suite to register and the body function/closure is the block of code that implements the suite. When applying BDD to your tests, this function is used to describe your story scenarios that you will implement. There are more arguments which you can see below:

Argument

Required

Default

Type

Description

title

true

---

string

The title of the suite to register

body

true

---

closure/udf

The closure that represents the test suite

labels

false

---

string/array

The list or array of labels this suite group belongs to

asyncAll

false

false

Boolean

If you want to parallelize the execution of the defined specs in this suite group.

skip

false

false

Boolean

A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean.

function run( testResults, testBox ){

     describe("A suite", function(){
          it("contains spec with an awesome expectation", function(){
               expect( true ).toBeTrue();
          });
     });

}

Bundles: Group Your Tests

TestBox relies on the fact of creating testing bundles which are basically CFCs. A bundle CFC will hold all the suites and specs a TestBox runner will execute and produce reports on.

component extends="testbox.system.BaseSpec"{

     // executes before all suites
     function beforeAll(){}

     // executes after all suites
     function afterAll(){}

     // All suites go in here
     function run( testResults, testBox ){

     }

}

This bundle CFC can contain 2 life-cycle functions and a single run() function where you will be writing your test suites and specs. The beforeAll() and afterAll() methods are called life-cycle methods. They will execute once before the run() function and once after the run() function. This is a great way to do any kind of global setup or teardown in your tests.

The run() function receives the TestBox testResults object as a reference and testbox as a reference as well. This way you can have metadata and access to what will be reported to users in a reporter. You can also use it to decorate the results or store much more information that can be picked up later by reports. You also have access to the testbox class so you can see how the test is supposed to execute, what labels was it passed, directories, options, etc.

https://www.ortussolutions.com/products/testbox
CommandBox CLI
Github

Ref Card

We have prepared an awesome PDF ref card for working with TestBox BDD:

Requirements

TestBox does not depend on any other framework for operation and it has a clean and descriptive syntax that will make you giggle when writing your tests. It ships with not only the xUnit and BDD testing capabilities, but also an assertions and expectations library, several different runners, reporters and MockBox, for mocking and stubbing capabilities.

Life-Cycle Methods

Before and After Specs

If you are familiar with xUnit style frameworks, the majority of them provide a way to execute functions before and after every single test case or spec in BDD. This is a great way to keep your tests DRY (Do not repeat yourself)! TestBox provides the beforeEach() and afterEach() methods that each take in a closure as their argument that receive the name of the spec that's about to be executed or just executed. As their names indicate, they execute before a spec and after a spec in a related describe block.

describe("A spec (with setup and tear-down)", function() {

     beforeEach(function( currentSpec ) {
          coldbox = 22;
          application.wirebox = new coldbox.system.ioc.Injector();
     });

     afterEach(function( currentSpec ) {
          coldbox = 0;
          structDelete( application, "wirebox" );
     });

     it("is just a function, so it can contain any code", function() {
          expect( coldbox ).toBe( 22 );
     });

     it("can have more than one expectation and talk to scopes", function() {
          expect( coldbox ).toBe( 22 );
          expect( application.wirebox.getInstance( 'MyService' ) ).toBeComponent();
     });
});

Around Specs

The aroundEach() life-cycle method will completely wrap your spec in another closure. This is an elegant way for you to provide a complete around AOP advice to a specification. You can use it to surround the execution in transaction blocks, ORM rollbacks, logging, and so much more. This life-cycle method will decorate ALL specs within a single suite and any children suites. The method signature is below:

aroundEach( function( spec, suite ){
     // execute the spec
     arguments.spec.body();
} );

The method receives a structure of data representing the spec. This contains the following elements:

  • body : The actual closure for the spec that you will use to execute within it.

  • labels : The labels used in the spec

  • name : The name of the spec

  • order : The order of execution of the spec

  • skip : The skip flag or closure that determines if the spec runs

The method also receives a structure of metadata about the suite this spec is contained in. It has information about life-cycle closures, async information, names, parents, etc. Here is a very practical example of creating and around each closure to provide rollbacks for specs:

aroundEach( function( spec, suite ){

     transaction action="begin"{
          try{/
               // execute the spec
               arguments.spec.body();
          } catch( Any e ){
               rethrow;
          } finally{
               transaction action="rollback";
          }
     }
} );

This simple around each life-cycle closure will rollback ALL my spec's executions even if they throw exceptions.

Annotations

Lifecycle Nesting Order

When you use beforeEach(), afterEach(), and aroundEach() at the same time, there is a specific order they fire in. For a given describe block, they will fire in this order. Remember, aroundEach() is split into two parts-- the half of the method before you call spec.body() and the second half of the method.

  1. beforeEach

  2. aroundEach (first half)

  3. it() (the spec.body() call)

  4. aroundEach (second half)

  5. afterEach()

Here's an example:

describe( 'my describe', function(){
	
    beforeEach( function( currentSpec ){
        // I run first
    } );
    	 
    aroundEach( function( spec, suite ){
        // I run second
        arguments.spec.body();
        // I run fourth
    });
    
    afterEach( function( currentSpec ){
        // I run fifth
    } );
    
    it( 'my it', function(){
        // I run third
    } );
    
} );

If there are more than one it() blocks, the process repeats for each one. Steps 1, 2, 4, 5 will wrap every single it().

When you nest more than one describe block inside the other, the before/around/after order is the same but drills down to the innermost describe and then bubbles back up. That means the outermost beforeEach() starts and we end on the outermost afterEach().

Here's what an example flow would look like that had before/after/around specified in two levels of describes with a single it() in the inner most describe.

  1. Outermost beforeEach() call

  2. Innermost beforeEach() call

  3. Outermost aroundEach() call (first half)

  4. Innermost aroundEach() call (first half)

  5. The it() block

  6. Innermost aroundEach() calls (second half)

  7. Outermost aroundEach() call (second half)

  8. Innermost afterEach() call

  9. Outermost afterEach() call

This works regardless of the number of levels and can obviously have many permutations, but the basic order is still the same. Before/around/after and starting at the outside working in, and back out again. This process happens for every single spec or it() block. This is as opposed to the beforeAll() and afterAll() method which only run once for the entire CFC regardless of how many specs there are.

TestBox since v2.3.0 also allows you to declare life-cycle methods via annotations. Please see our section for more information.

Annotations

Given-When-Then Blocks

Feature: Box Size
    In order to know what size box I need
    As a distribution manager
    I want to know the volume of the box

    Scenario: Get box volume
        Given I have entered a width of 20
        And a height of 30
        And a depth of 40
        When I run the calculation
        Then the result should be 24000

TestBox provides you with feature(), scenario() and story() wrappers for describe() blocks. As such we can write our requirements in test form like so:

feature( "Box Size", function(){

    describe( "In order to know what size box I need
              As a distribution manager
              I want to know the volume of the box", function(){

        scenario( "Get box volume", function(){
            given( "I have entered a width of 20
                And a height of 30
                And a depth of 40", function(){
                when( "I run the calculation", function(){
                      then( "the result should be 24000", function(){
                          // call the method with the arguments and test the outcome
                          expect( myObject.myFunction(20,30,40) ).toBe( 24000 );
                      });
                 });
            });
        });
    });
});

The output from running the test will read as the original requirements, providing you with not only automated tests but also a living document of the requirements in a business-readable format.

story("As a distribution manager, I want to know the volume of the box I need", function() {
    given("I have a width of 20
        And a height of 30
        And a depth of 40", function() {
        when("I run the calculation", function() {
              then("the result should be 24000", function() {
                  // call the method with the arguments and test the outcome
                  expect(myObject.myFunction(20,30,40)).toBe(24000);
              });
         });
    });
});

As feature(), scenario() and story() are wrappers for describe() you can intermix them so that your can create tests which read as the business requirements. As with describe(), they can be nested to build up blocks.

Suite Groups

describe("A spec", function() {
     it("is just a closure, so it can contain any code", function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
     });

     it("can have more than one expectation", function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
          expect( coldbox ).toBeTrue();
     });
});

Nesting describe Blocks

Calls to our describe() function can be nested with specs at any level or point of execution. This allows you to create your tests as a related tree of nested functions. Please note that before a spec is executed, TestBox walks down the tree executing each beforeEach() and afterEach() function in the declared order. This is a great way to logically group specs in any level as you see fit.

describe("A spec", function() {

     beforeEach(function( currentSpec ) {
          coldbox = 22;
          application.wirebox = new coldbox.system.ioc.Injector();
     });

     afterEach(function( currentSpec ) {
          coldbox = 0;
          structDelete( application, "wirebox" );
     });

     it("is just a function, so it can contain any code", function() {
          expect( coldbox ).toBe( 22 );
     });

     it("can have more than one expectation and talk to scopes", function() {
          expect( coldbox ).toBe( 22 );
          expect( application.wirebox.getInstance( 'MyService' ) ).toBeComponent();
     });

     describe("nested inside a second describe", function() {

          beforeEach(function( currentSpec ) {
               awesome = 22;
          });

          afterEach(function( currentSpec ) {
               awesome = 22 + 8;
          });

          it("can reference both scopes as needed ", function() {
            expect( coldbox ).toBe( awesome );
          });
     });

     it("can be declared after nested suites and have access to nested variables", function() {
          expect( awesome ).toBe( 30 );
     });

});

Specs

Argument

Required

Default

Type

Description

title

true

---

string

the title of the spec

body

true

---

closure/udf

The closure that represents the spec

labels

false

---

string/array

The list or array of labels this suite group belongs to

skip

false

false

Boolean

A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean.

data

false

{}

struct

A struct of data you can bind the spec with so you can use within the body closure

An expectation is a nice assertion DSL that TestBox exposes so you can pretty much read what should happen in the testing scenario. A spec will pass if all expectations pass. A spec with one or more expectations that fail will fail the entire spec.

function run(){

     describe("A suite", function(){
          it("contains spec with an awesome expectation", function(){
               expect( true ).toBeTrue();
          });

          it("contains a spec with more than 1 expectation", function(){
               expect( [1,2,3] ).toBeArray();
               expect( [1,2,3] ).toHaveLength( 3 );
          });
     });

}

They are closures Ma!

function run(){

     describe("A suite is a closure", function(){
          c = new Calculator();

          it("and so is a spec", function(){
               expect( c ).toBeTypeOf( 'component' );
          });
     });

}

Spec Data Binding

The data argument can be used to pass in a structure of data into the spec so it can be used later within the body closure. This is great when doing looping and creating dynamic closure calls:

// Simple Example
it( title="can handle binding", body=function( data ){
    expect(    data.keep ).toBeTrue();
}, data={ keep = true } );

// Complex Example. Let's iterate over a bunch of files and create dynamic specs
for( var filePath in files ){

  it( 
    title="#getFileFromPath( filePath )# should be valid JSON", 
    // pass in a struct of data to the spec for later evaluation
    data={ filePath = filePath },
    // the spec closure accepts the data for later evaluation
    body=function( data ) {
      var json = fileRead( data.filePath );
      var isItJson = isJSON( json );

      expect( json ).notToBeEmpty();
      expect( isItJson ).toBeTrue();

      if( isItJson ){
          var jsonData = deserializeJSON(json);
          if( getFileFromPath( filePath ) != "index.json"){
              expect( jsonData ).toHaveKey( "name" );
              expect( jsonData ).toHaveKey( "type" );
          }
      }

  });
}

Dynamic Suites

With TestBox's BDD syntax, it is possible to create suites dynamically; however, there are a few things to be aware of.

Setup for dynamic suites must be done in the pseudo-constructor (versus in beforeAll()). This is because variables-scoped variables set in beforeAll() are not available in the describe closure (even though they are available in it closures). This behavior can be explained by the execution sequence of a BDD bundle: When the bundle's run() method is called, it collects preliminary test data via describes. After preliminary test data are collected, the beforeAll() runs, followed by the describe closures.

Additionally, care must be taken to pass data into the it closures, otherwise strange behavior will result (the values from the last loop iteration will be repeated in the body of each looped it).

Example

The following bundle creates suites dynamically, by looping over test metadata.

Expectations

Expectations are self-concatenated strings that evaluate an actual value to an expected value or condition. These are initiated by the global TestBox method called expect() which takes in a value called the actual value or expectAll() which takes in an array or struct which will be the actual value. It is concatenated in our expectations DSL with a matcher function that will most likely take in an expected value or condition to test. You can also concatenate the matchers and do multiple evaluations on a single actual value.

Matchers

Each matcher implements a comparison or evaluation of the actual value and an expected value or condition. It is responsible for either passing or failing this evaluation and reporting it to TestBox. Each matcher also has a negative counterpart assertion by just prefixing the call to the matcher with a not expression.

Included Matchers

Custom Matchers

Skipping Specs and Suites

Specs and suites can be skipped from execution by prefixing certain functions with the letter x or by using the skip argument in each of them. The reporters will show that these suites or specs where skipped from execution. The functions you can prefix are:

  • it()

  • describe()

  • story()

  • given()

  • when()

  • then()

  • feature()

Skip Argument

The skip argument can be a boolean value or a closure. If the value is true then the suite or spec is skipped. If the return value of the closure is true then the suite or spec is skipped. Using the closure approach allows you to dynamically at runtime figure out if the desired spec or suite is skipped. This is such a great way to prepare tests for different CFML engines.

Specs and Suite Labels

Specs and suites can be tagged with TestBox labels. Labels allows you to further categorize different specs or suites so that when a runner executes with labels attached, only those specs and suites will be executed, the rest will be skipped.

TestBox
Ortus Solutions, Corp
TestBox
Ortus Solutions, Corp
Shalom Children's Home
Debug Panel
Bundle Filter

is a style of writing tests where you describe the state of the code you want to test (Given), the behavior you want to test (When) and the expected outcome (Then). (See )

Testbox supports the use of function names given() and when() in-place of describe() function calls. The then() function call is an alternative for it() function calls. The advantage of this style of is that you can gather your requirements and write your tests in a common language that can be understood by developers and stake-holders alike. This common language format is often referred to as the language; using it we can gather and document the requirements as:

If you prefer to gather requirements as then you may prefer to take advantage of the story() wrapper for describe() instead.

As we have seen before, the describe() function describes a test suite of related specs in a test bundle CFC. The title of the suite is concatenated with the title of a spec to create a full spec's name which is very descriptive. If you name them well, they will read out as full sentences as defined by style.

Specifications (Specs) are defined by calling the TestBox it() global function, which takes in a title and a body function/closure. The title is the title of this spec you will write and the body function/closure is a block of code that represents the spec. A spec will contain most likely one or more expectations that will test the state of the (software under test) or sometimes referred to as code under test. In BDD style, your specifications are what is used to validate your requirements of a scenario which is your describe() block of your .

Since the implementations of the describe() and it() functions are closures, they can contain executable code that is necessary to implement the test. All CFML rules of scoping apply to , so please remember them. We recommend always using the variables scope for easy access and distinction.

TestBox has a plethora (That's Right! I said ) of matchers that are included in TestBox. The best way to see all the latest matchers is to visit our and digest the testbox.system.Expectation class. There is also the ability to register and write custom matchers in TestBox via our addMatchers() function at runtime.

You can also build and register custom matchers. Please visit the Custom Matchers chapter to read more about .

Given-When-Then
Specification By Example
behavioural specifications
Gherkin
User Stories
BDD
SUT
story
closures
component
    extends="testbox.system.BaseSpec"
    hint="This is an example of a TestBox BDD test bundle containing dynamically-defined suites."
{

    /*
    * Need to do config for *dynamic* test suites here, in the
    * pseudo-constructor, versus in `beforeAll()`.
    */
    doDynamicSuiteConfig();

    /*
    * @hint This method is arbitrarily named, but it sets up 
    * metadata needed by the dynamic suites example. The setup
    * could have been done straight in the pseudo-constructor,
    * but it might be nice to organize it into such a method
    * as this.
    */
    function doDynamicSuiteConfig(){
        variables.dynamicSuiteConfig = ["foo","bar","baz"];
    }

    function run( testResults, testBox ){

        /*
        * @hint Dynamic Test Suites Example
        */
        // loop over test metadata
        for ( var thing in dynamicSuiteConfig ) {
            describe("Dynamic Suite #thing#", function(){
                // notice how data is passed into the it() closure:
                //  * data={ keyA=valueA, keyB=ValueB }
                //  * function( data )
                it( title=thing & "test", 
                    data={ thing=thing }, 
                    body=function( data ) {
                      var thing = data.thing;
                      expect( thing ).toBe( thing );
                });
            });
        }

    }

}
function run(){

     describe("The 'toBe' matcher evaluates equality", function(){
          it("and has a positive case", function(){
               expect( true ).toBe( true );
          });

          it("and has a negative case", function(){
               expect( false ).notToBe( true );
          });
     });

     describe("Collection expectations", function(){
        it( "can be done easily with TestBox", function(){
            expectAll( {a:2,b:4,c:6} ).toSatisfy( function(x){ return 0 == x%2; });
        });
    });

}
describe("Some Included TestBox Matchers:", function() {

     describe("The 'toBe' matcher", function() {
          it("works for simple values", function() {
               coldbox = 1;
               expect( coldbox )
                    .toBe( 1 )
                    .notToBe( 5 );
          });

          it("works for complex values too (arrays,structs,queries,objects)", function() {
               expect( [1,2] ).toBe( [1,2] );
               expect( { name="luis", awesome=true} ).toBe( { awesome=true, name="luis" } );
               expect( this ).toBe( this );
               expect( queryNew("") ).toBe( queryNew("") );
          });
     });

     it("The 'toBeWithCase' matches strings with case equality", function() {
          expect( 'hello' )
               .toBeWithCase( 'hello' )
               .notToBeWithCase( 'HELLO' );
     });

     it("The 'toBeTrue' and 'toBeFalse' matchers are used for boolean operations", function() {
          coldbox_rocks = true;
          expect( coldbox_rocks ).toBeTrue();
          expect( !coldbox_rocks ).toBeFalse();
     });

     it("The 'toBeNull' expects null values", function() {
          foo = "bar";
          expect( javaCast("null", "") ).toBeNull();
          expect( foo ).notToBeNull();
     });

     it("The 'toBeInstanceOf' expects the object to be of the same class or inheritance or implementation", function() {
          expect( new coldbox.system.Assertions ).toBeInstanceOf( 'coldbox.system.Assertions' );
          expect( this ).notToBeInstanceOf( 'coldbox.system.MockBox' );
     });

     it("The 'toMatch' matcher is for regular expressions with case sensitivity", function() {
          message = 'foo man choo';

          expect( message )
               .toMatch( '^foo' )
               .toMatch( '(man)' )
               .notToMatch( 'superman' );
     });

     it("The 'toMatchNoCase' matcher is for regular expressions with no case sensitivity", function() {
          message = 'foo man choo';

          expect( message )
               .toMatch( '^FOO' )
               .toMatch( '(MAN)' )
               .notToMatch( 'SuperMan' );
     });

     describe("The 'toBeTypeOf' matcher evaluates using the CF isValid() function", function() {
          it("works with direct calls", function() {
               expect( [1,2] ).toBeTypeOf( 'Array' );
               expect( { name="luis", awesome=true} ).toBeTypeOf( 'struct' );
               expect( this ).toBeTypeOf( 'component' );
               expect( '03/01/1990' ).toBeTypeOf( 'usdate' );
          });

          it("works with dynamic calls as well", function() {
               expect( [1,2] ).toBeArray();
               expect( { name="luis", awesome=true} ).toBeStruct();
               expect( this ).toBeComponent();
               expect( '03/01/1990' ).toBeUsDate();
          });
     });

     it("The 'toBeEmpty' checks emptyness of simple or complex values", function() {
          expect( [] ).toBeEmpty();
          expect( { name="luis", awesome=true} ).notToBeEmpty();
          expect( '' ).toBeEmpty();
     });

     it("The 'toHaveLength' checks size of simple or complex values", function() {
          expect( [] ).toHaveLength( 0 );
          expect( { name="luis", awesome=true} ).toHaveLength( 2 );
          expect( 'Hello' ).toHaveLength( 5 );
     });

     it("The 'toHaveKey' checks for existence of keys in structs", function() {
          expect( { name="luis", awesome=true} ).toHaveKey( 'name' );
     });

     it("The 'toHaveDeepKey' checks for existence of keys anywhere in structs", function() {
          expect( { name="luis", { age=35, awesome=true } } ).toHaveDeepKey( 'age' );
     });

     it("The 'toThrow' checks if the actual call fails", function() {
          expect( function(){
               new calculator().divide( 40, 0 );
          }).toThrow();

          expect( function(){
               new calculator().divide( 40, 0 );
          }).toThrow( regex="zero" );
     });

});
xdescribe("A spec", function() {
     it("was just skipped, so I will never execute", function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
     });
});

describe("A spec", function() {
     it("is just a closure, so it can contain any code", function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
     });

     xit("can have more than one expectation, but I am skipped", function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
          expect( coldbox ).toBeTrue();
     });
});
describe(title="A railo suite", body=function() {
     it("can be expected to run", function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
     });

     it(title="can have more than one expectation and another skip closure", body=function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
          expect( coldbox ).toBeTrue();

     },skip=function(){
          return false;
     });

},skip=function(){
     return !structKeyExists( server, "railo" );
});
describe(title="A spec", labels="stg,railo", body=function() {
     it("executes if its in staging or in railo", function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
     });
});

describe("A spec", function() {
     it("is just a closure, so it can contain any code", function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
     });

     it(title="can have more than one expectation and labels", labels="dev,stg,qa,shopping", body=function() {
          coldbox = 0;
          coldbox++;

          expect( coldbox ).toBe( 1 );
          expect( coldbox ).toBeTrue();
     });
});
Plethora
API
custom matchers

Asynchronous Testing

As you can see from our arguments for a test suite, you can pass an asyncAll argument to the describe() blocks that will allow TestBox to execute all specs in separate threads for you concurrently.

Caution Once you delve into the asynchronous world you will have to make sure your tests are also thread safe (var-scoped) and provide any necessary locking.

describe(title="A spec (with setup and tear-down)", asyncAll=true, body=function() {

     beforeEach(function() {
          coldbox = 22;
          application.wirebox = new coldbox.system.ioc.Injector();
     });

     afterEach(function() {
          coldbox = 0;
          structDelete( application, "wirebox" );
     });

     it("is just a function, so it can contain any code", function() {
          expect( coldbox ).toBe( 22 );
     });

     it("can have more than one expectation and talk to scopes", function() {
          expect( coldbox ).toBe( 22 );
          expect( application.wirebox.getInstance( 'MyService' ) ).toBeComponent();
     });
});

TestBox xUnit Primer

TestBox is a xUnit style and behavior-driven development (BDD) testing framework for CFML (ColdFusion). It does not depend on any other framework for operation and it has a clean and descriptive syntax that will make you giggle when writing your tests. It ships with not only the xUnit and BDD testing capabilities, but also an assertions and expectations library, several different runners, reporters and MockBox, for mocking and stubbing capabilities.

Info Note: This article is a primer to get you started with the xUnit capabilities of TestBox, if you would like to explore the BDD style capabilities of TestBox we recommend you visit the BDD primer chapter.

Running Tests

TestBox ships with several test runners internally but we have tried to simplify and abstract it with our testbox object which can be found in the testbox.system package. The TestBox object allows you to execute tests from a variety of entry points and languages such as CFC, CFM, HTTP, NodeJS, SOAP or REST. You can also make your CFC's extend from our BaseSpec class so you can execute it directly via the URL. The main execution methods are:

// Create TestBox object
testbox = new testbox.system.TestBox();

// You can add fluent specs via addDirectory(), addDirectories(), addBundles()
testbox.addDirectory( "/tests/specs" );

// Run tests and produce reporter results
testbox.run()

// Run tests and get raw testbox.system.TestResults object
testbox.runRaw()

// Run tests and produce reporter results from SOAP, REST, HTTP
testbox.runRemote()

// Run via Spec URL
http://localhost/test/spec.cfc?method=runRemote

Info We encourage you to read the API docs included in the distribution for the complete parameters for each method.

run() Arguments

Here are the arguments you can use for initializing TestBox or executing the run() method

Argument

Required

Default

Type

Description

bundles

true

---

string/array

The path, list of paths or array of paths of the spec bundle CFCs to run and test

directory

false

---

struct

The directory mapping path or a struct: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the CFC found, it must return true to process or false to continue process ]

reporter

false

simple

string/struct/instance

The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or an instance of a coldbox.system.reports.IReporter. You can also pass a struct with [type="string or classpath", options={}] if a reporter expects options.

labels

false

false

string/array

The string or array of labels the runner will use to execute suites and specs with.

excludes

false

---

string/array

The string or array of labels to exclude from running

options

false

{}

struct

A structure of property name-value pairs that each runner can implement and use at its discretion.

testBundles

false

---

string/array

A list or array of bundle names that are the ones that will be executed ONLY!

testSuites

false

---

string/array

A list or array of suite names that are the ones that will be executed ONLY!

testSpecs

false

---

string/array

A list or array of test names that are the ones that will be executed ONLY

callbacks

false

{}

struct of closures or CFC

A struct of listener callbacks or a CFC with callbacks for listening to progress of the testing: onBundleStart,onBundleEnd,onSuiteStart,onSuiteEnd,onSpecStart,onSpecEnd

eagerFailure

false

false

boolean

If true, then after testing a bundle if there are any failures or errors no more testing will be performed.

runRemote() arguments

Here are the arguments you can use for executing the runRemote() method:

Argument

Required

Default

Type

Description

bundles

true

---

string

The path, list of paths or array of paths of the spec bundle CFCs to run and test

directory

false

---

string

The directory mapping to test: directory = the path to the directory using dot notation (myapp.testing.specs)

recurse

false

true

boolean

Recurse the directory mapping or not, by default it does

reporter

false

simple

string/path

The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or a class path to the reporter to use.

reporterOptions

false

{}

JSON

A JSON struct literal of options to pass into the reporter

labels

false

false

string

The string array of labels the runner will use to execute suites and specs with.

excludes

false

---

string/array

The string or array of labels to exclude from running

options

false

{}

JSON

A JSON struct literal of configuration options that are optionally used to configure a runner.

testBundles

false

---

string/array

A list or array of bundle names that are the ones that will be executed ONLY!

testSuites

false

---

string

A list of suite names that are the ones that will be executed ONLY!

testSpecs

false

---

string

A list of test names that are the ones that will be executed ONLY

eagerFailure

false

false

boolean

If true, then after testing a bundle if there are any failures or errors no more testing will be performed.

The bundles argument which can be a single CFC path or an array of CFC paths or a directory argument so it can go and discover the test bundles from that directory. The reporter argument can be a core reporter name like: json,xml,junit,raw,simple,dots,tap,min,etc or it can be an instance of a reporter CFC. You can execute the runners from any cfm template or any CFC or any URL, that is up to you.

Global Runner

TestBox ships with a global runner that can be used to run pretty much anything. You can customize it or place it wherever you need it:

Test Browser

TestBox ships with a test browser that is highly configurable to whatever URL accessible path you want. It will then show you a test browser where you can navigate and execute not only individual tests, but also directory suites as well.

ANT Runner

In our test samples and templates we include an ANT runner that will be able to execute your tests via ANT. It can also leverage our ANTJunit reporter to use the junitreport task to produce JUnit compliant reports as well. You can find this runner in the test samples and runner template directory.

Bundle(s) Runner

<cfset r = new coldbox.system.TestBox( "coldbox.testing.cases.testing.specs.BDDTest" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", testSpecs="OnlyThis,AndThis,AndThis" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", testSuites="Custom Matchers,A Spec" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( [ "coldbox.testing.cases.testing.specs.BDDTest", "coldbox.testing.cases.testing.specs.BDD2Test" ] ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", labels="railo" ) >
<cfoutput>#r.run(reporter="json")#</cfoutput>

Test Runner

Directory Runner

<cfset r = new coldbox.system.TestBox( directory="coldbox.testing.cases.testing.specs" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( directory={ mapping="coldbox.testing.cases.testing.specs", recurse=false } ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox(
      directory={ mapping="coldbox.testing.cases.testing.specs",
      recurse=true,
      filter=function(path){
            return ( findNoCase( "test", arguments.path ) ? true : false );
      }}) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox(
      directory={ mapping="coldbox.testing.cases.testing.specs",
      recurse=true,
      filter=function(path){
            return ( findNoCase( "test", arguments.path ) ? true : false );
      }}) >
<cfset fileWrite( 'testreports.json', r.run() )>

SOAP Runner

You can run tests via SOAP by leveraging the runRemote() method. The WSDL URL will be

http://localhost/testbox/system/TestBox.cfc?wsdl

HTTP/REST Runner You can run tests via HTTP/REST by leveraging the runRemote() endpoint. The URL will be

http://localhost/testbox/system/TestBox.cfc

NodeJS Runner

There is a user-contributed NodeJS Runner that looks fantastic and can be downloaded here:

Just use node to install:

npm install -g testbox-runner

RefCard

We have prepared an awesome PDF ref card for working with TestBox xUnit:

Reporters

TestBox comes also with a nice plethora of reporters:

  • ANTJunit : A specific variant of JUnit XML that works with the ANT junitreport task

  • Codexwiki : Produces MediaWiki syntax for usage in Codex Wiki

  • Console : Sends report to console

  • Doc : Builds semantic HTML to produce nice documentation

  • Dot : Builds an awesome dot report

  • JSON : Builds a report into JSON

  • JUnit : Builds a JUnit compliant report

  • Raw : Returns the raw structure representation of the testing results

  • Simple : A basic HTML reporter

  • Text : Back to the 80's with an awesome text report

  • XML : Builds yet another XML testing report

  • Tap : A test anything protocol reporter

  • Min : A minimalistic view of your test reports

  • MinText : A minimalistic view of your test reports in consoles

If you make your test bundle CFC inherit from our testbox.system.BaseSpec class, you will be able to execute the CFC directly via the URL: ```javascript ``` You can also pass the following arguments to the method via the URL: * **testSuites** : A list or array of suite names that are the ones that will be executed ONLY! * **testSpecs** : A list or array of test names that are the ones that will be executed ONLY! * **reporter** : The type of reporter to run the test with ```javascript ```

NodeJS : User-contributed:

http://localhost/test/MyTest.cfc?method=runRemote
http://localhost/test/MyTest.cfc?method=runRemote&reporter=json
https://www.npmjs.com/package/testbox-runner

Requirements

The xUnit functionalities of TestBox will require Railo 4.1+, Lucee 4.5+ or ColdFusion 9.01+.

https://www.npmjs.com/package/testbox-runner

Bundles: Group Your Tests

TestBox relies on the fact of creating testing bundles which are basically CFCs. A bundle CFC will hold all the tests the TestBox runner will execute and produce reports on. Thus, sometimes this test bundle is referred to as a test suite in xUnit terms.

component displayName="My test suite" extends="testbox.system.BaseSpec"{

     // executes before all tests
     function beforeTests(){}

     // executes after all tests
     function afterTests(){}

}

This bundle can contain 2 life-cycle methods, the beforeTests() and afterTests() methods will execute once before ALL your tests run and then after ALL your tests run. This is a great way to do any kind of global setup or teardown in your tests. It also contains a displayName argument in the component declaration which gives you a way to name your testing suite. We will see later the other annotations you can add to the component declaration further in the primer.

Assertions

Assertions are self-concatenated strings that evaluate an actual value to an expected value or condition. These are initiated by the global TestBox variable called $assert which contains tons of included assertion methods so you can evaluate your tests.

Evaluators

Each assertion evaluator will compare the actual value and an expected value or condition. It is responsible for either passing or failing this evaluation and reporting it to TestBox. Each evaluator also has a negative counterpart assertion by just prefixing the call to the method with a not expression.

      function testIncludes(){
          $assert.includes( "hello", "HE" );
          $assert.includes( [ "Monday", "Tuesday" ] , "monday" );
     }

     function testNotIncludes(){
          $assert.notIncludes( "hello", "what" );
          $assert.notIncludes( [ "Monday", "Tuesday" ] , "Friday" );
     }

Included Evaluators

component displayName="TestBox xUnit suite for CF9" labels="railo,cf"{

/*********************************** LIFE CYCLE Methods ***********************************/

     function beforeTests(){
          application.salvador = 1;
     }

     function afterTests(){
          structClear( application );
     }

     function setup(){
          request.foo = 1;
     }

     function teardown(){
          structClear( request );
     }

/*********************************** Test Methods ***********************************/

     function testIncludes(){
          $assert.includes( "hello", "HE" );
          $assert.includes( [ "Monday", "Tuesday" ] , "monday" );
     }

     function testIncludesWithCase(){
          $assert.includesWithCase( "hello", "he" );
          $assert.includesWithCase( [ "Monday", "Tuesday" ] , "Monday" );
     }

     function testnotIncludesWithCase(){
          $assert.notincludesWithCase( "hello", "aa" );
          $assert.notincludesWithCase( [ "Monday", "Tuesday" ] , "monday" );
     }

     function testNotIncludes(){
          $assert.notIncludes( "hello", "what" );
          $assert.notIncludes( [ "Monday", "Tuesday" ] , "Friday" );
     }

     function testIsEmpty(){
          $assert.isEmpty( [] );
          $assert.isEmpty( {} );
          $assert.isEmpty( "" );
          $assert.isEmpty( queryNew("") );
     }

     function testIsNotEmpty(){
          $assert.isNotEmpty( [1,2] );
          $assert.isNotEmpty( {name="luis"} );
          $assert.isNotEmpty( "HelloLuis" );
          $assert.isNotEmpty( querySim( "id, name
               1 | luis") );
     }

     function testSkipped() skip{
          $assert.fail( "This Test should fail" );
     }

     boolean function isRailo(){
          return structKeyExists( server, "railo" );
     }

     function testSkippedWithConstraint() skip="isRailo"{
          $assert.fail( "This Test should fail" );
     }

     function testFails(){
          //$assert.fail( "This Test should fail" );
     }

     function testFailsShortcut() labels="railo"{
          //fail( "This Test should fail" );
     }

     function testAssert() {
          $assert.assert( application.salvador == 1 );
     }

     function testAssertShortcut() {
          assert( application.salvador == 1 );
     }

     function testisTrue() {
          $assert.isTrue( 1 );
     }

     function testisFalse() {
          $assert.isFalse( 0 );
     }

     function testisEqual() {
          $assert.isEqual( 0, 0 );
          $assert.isEqual( "hello", "HEllO" );
          $assert.isEqual( [], [] );
          $assert.isEqual( [1,2,3, {name="hello", test="this"} ], [1,2,3, {test="this", name="hello"} ] );
     }

     function testisNotEqual() {
          $assert.isNotEqual( this, new coldbox.system.MockBox() );
          $assert.isNotEqual( "hello", "test" );
          $assert.isNotEqual( 1, 2 );
          $assert.isNotEqual( [], [1,3] );
     }

     function testisEqualWithCase() {
          $assert.isEqualWithCase( "hello", "hello" );
     }

     function testnullValue() {
          $assert.null( javaCast("null", "") );
     }

     function testNotNullValue() {
          $assert.notNull( 44 );
     }

     function testTypeOf() {
          $assert.typeOf( "array", [ 1,2 ] );
          $assert.typeOf( "boolean", false );
          $assert.typeOf( "component", this );
          $assert.typeOf( "date", now() );
          $assert.typeOf( "time", timeformat( now() ) );
          $assert.typeOf( "float", 1.1 );
          $assert.typeOf( "numeric", 1 );
          $assert.typeOf( "query", querySim( "id, name
               1 | luis") );
          $assert.typeOf( "string", "hello string" );
          $assert.typeOf( "struct", { name="luis", awesome=true } );
          $assert.typeOf( "uuid", createUUID() );
          $assert.typeOf( "url", "http://www.coldbox.org" );
     }

     function testNotTypeOf() {
          $assert.notTypeOf( "array", 1 );
          $assert.notTypeOf( "boolean", "hello" );
          $assert.notTypeOf( "component", {} );
          $assert.notTypeOf( "date", "monday" );
          $assert.notTypeOf( "time", "1");
          $assert.notTypeOf( "float", "Hello" );
          $assert.notTypeOf( "numeric", "eeww2" );
          $assert.notTypeOf( "query", [] );
          $assert.notTypeOf( "string", this );
          $assert.notTypeOf( "struct", [] );
          $assert.notTypeOf( "uuid", "123" );
          $assert.notTypeOf( "url", "coldbox" );
     }

     function testInstanceOf() {
          $assert.instanceOf( new coldbox.system.MockBox(), "coldbox.system.MockBox" );
     }

     function testNotInstanceOf() {
          $assert.notInstanceOf( this, "coldbox.system.MockBox" );
     }

     function testMatch(){
          $assert.match( "This testing is my test", "(TEST)$" );
     }

     function testMatchWithCase(){
          $assert.match( "This testing is my test", "(test)$" );
     }

     function testNotMatch(){
          $assert.notMatch( "This testing is my test", "(hello)$" );
     }

     function testKey(){
          $assert.key( {name="luis", awesome=true}, "awesome" );
     }

     function testNotKey(){
          $assert.notKey( {name="luis", awesome=true}, "test" );
     }

     function testDeepKey(){
          $assert.deepKey( {name="luis", awesome=true, parent = { age=70 } }, "age" );
     }

     function testNotDeepKey(){
          $assert.notDeepKey( {name="luis", awesome=true, parent = { age=70 } }, "luis" );
     }

     function testLengthOf(){
          $assert.lengthOf( "heelo", 5 );
          $assert.lengthOf( [1,2], 2 );
          $assert.lengthOf( {name="luis"}, 1 );
          $assert.lengthOf( querySim( "id, name
               1 | luis"), 1 );

     }

     function testNotLengthOf(){
          $assert.notLengthOf( "heelo", 3 );
          $assert.notLengthOf( [1,2], 5 );
          $assert.notLengthOf( {name="luis"}, 5 );
          $assert.notLengthOf( querySim( "id, name
               1 | luis"), 0 );

     }

     // railo 4.1+ or CF10+
      function testThrows(){
          $assert.throws(function(){
               var hello = invalidFunction();
          });
     }

      // railo 4.1+ or CF10+
     function testNotThrows(){
          $assert.notThrows(function(){
               var hello = 1;
          });
     }

/*********************************** NON-RUNNABLE Methods ***********************************/

     function nonStandardNamesWillNotRun() {
          fail( "Non-test methods should not run" );
     }

     private function privateMethodsDontRun() {
          fail( "Private method don't run" );
     }

}

Custom Assertions

You can also register custom assertions within the $assert object. You will do this by reading our Custom Assertions section of our TestBox docs.

TestBox has a plethora (That's Right! I said Plethora) of evaluators that are included in the release. The best way to see all the latest evaluator methods is to visit our and digest the coldbox.system.Assertion class. There is also the ability to register and write custom assertion evaluators in TestBox via our addAssertions() function.

API

Test Methods

TestBox discovers test methods in your bundle CFC by applying the following discovery rules:

  • Any method that has a test annotation on it

  • Any public method that starts or ends with the word test

// Via inline annotation
function shouldBeAwesome() test{}

/**
* Via comment annotation
* @test
*/
function shouldBeAwesome(){}

// via conventions
function testShouldDoThis(){}
function shouldDoThisTest(){}

Each test method will test the state of the SUT (software under test) or sometimes referred to as code under test. It will do so by asserting that actual values from an execution match an expected value or condition. TestBox offers an assertion library that you have available in your bundle via the injected variable $assert. You can also use our expectations library if you so desire, but that is mostly used in our BDD approach.

function testIncludes(){
       $assert.includes( "hello", "HE" );
       $assert.includes( [ "Monday", "Tuesday" ] , "monday" );
}

Each test function can also have some cool annotations attached to it.

Argument

Required

Default

Type

Description

labels

false

---

string/list

The list of labels this test belongs to

skip

false

false

boolean/udf

A boolean flag that makes the runners skip the test for execution. It can also be the name of a UDF in the same CFC that will be executed and MUST return a boolean value.

Spies & Mocking

  • makePublic( target, method, newName ) - Exposes private methods from objects as public methods

  • querySim( queryData ) - Simulate a query

  • getMockBox( [generationPath] ) - Get a reference to MockBox

  • createEmptyMock( [className], [object], [callLogging=true]) - Create an empty mock from a class or object

  • createMock( [className], [object], [clearMethods=false], [callLogging=true]) - Create a spy from an instance or class with call logging

  • prepareMock( object, [callLogging=true]) - Prepare an instance of an object for method spies with call logging

  • createStub( [callLogging=true], [extends], [implements]) - Create stub objects with call logging and optional inheritance trees and implementation methods

  • getProperty( target, name, [scope=variables], [defaultValue] ) - Get a property from an object in any scope

Asynchronous-Testing

You can tag a bundle component declaration with the boolean asyncAll annotation and TestBox will execute all specs in separate threads for you concurrently.

Caution Once you delve into the asynchronous world you will have to make sure your tests are also thread safe (var-scoped) and provide any necessary locking.

Test and Suite Labels

Tests and suites can be tagged with TestBox labels. Labels allows you to further categorize different tests or suites so that when a runner executes with labels attached, only those tests and suites will be executed, the rest will be skipped. Labels can be applied globally to the component declaration of the test bundle suite or granularly at the test method declaration.

Please refer to our section to take advantage of all the mocking and stubbing you can do. However, every BDD TestBundle has the following functions available to you for mocking and stubbing purposes:

MockBox
component displayName="TestBox xUnit suite" skip="testEnv" asyncAll=true{

     function setup(){
          application.wirebox = new coldbox.system.ioc.Injector();
          structClear( request );
     }

     function teardown(){
          structDelete( application, "wirebox" );
          structClear( request );
     }

     function testThrows() skip="true"{
          $assert.throws(function(){
               var hello = application.wirebox.getInstance( "myINvalidService" ).run();
          });
     }

     function testNotThrows(){
          $assert.notThrows(function(){
               var hello = application.wirebox.getInstance( "MyValidService" ).run();;
          });
     }

     private boolean function testEnv(){
          return ( structKeyExists( request, "env") && request.env == "stg" ? true : false );
     }

}
component displayName="TestBox xUnit suite" labels="railo,stg,dev"{

     function setup(){
          application.wirebox = new coldbox.system.ioc.Injector();
          structClear( request );
     }

     function teardown(){
          structDelete( application, "wirebox" );
          structClear( request );
     }

     function testThrows(){
          $assert.throws(function(){
               var hello = application.wirebox.getInstance( "myINvalidService" ).run();
          });
     }

     function testNotThrows(){
          $assert.notThrows(function(){
               var hello = application.wirebox.getInstance( "MyValidService" ).run();;
          });
     }

     function testFailsShortcut() labels="dev"{
          fail( "This Test should fail when executed with labels" );
     }

}

Skipping Tests and Suites

Tests and suites can be skipped from execution by using the skip annotation in the component or function declaration. The reporters will show that these suites or tests where skipped from execution. The value of the skip annotation can be a simple true or false or it can be the name of a UDF that exists in the same bundle CFC. This UDF must return a boolean value and it is evaluated at runtime.

component displayName="TestBox xUnit suite" skip="testEnv"{

     function setup(){
          application.wirebox = new coldbox.system.ioc.Injector();
          structClear( request );
     }

     function teardown(){
          structDelete( application, "wirebox" );
          structClear( request );
     }

     function testThrows() skip="true"{
          $assert.throws(function(){
               var hello = application.wirebox.getInstance( "myINvalidService" ).run();
          });
     }

     function testNotThrows(){
          $assert.notThrows(function(){
               var hello = application.wirebox.getInstance( "MyValidService" ).run();;
          });
     }

     private boolean function testEnv(){
          return ( structKeyExists( request, "env") && request.env == "stg" ? true : false );
     }

}

Spies and Mocking

  • makePublic( target, method, newName ) - Exposes private methods from objects as public methods

  • querySim( queryData ) - Simulate a query

  • getMockBox( [generationPath] ) - Get a reference to MockBox

  • createEmptyMock( [className], [object], [callLogging=true]) - Create an empty mock from a class or object

  • createMock( [className], [object], [clearMethods=false], [callLogging=true]) - Create a spy from an instance or class with call logging

  • prepareMock( object, [callLogging=true]) - Prepare an instance of an object for method spies with call logging

  • createStub( [callLogging=true], [extends], [implements]) - Create stub objects with call logging and optional inheritance trees and implementation methods

  • getProperty( target, name, [scope=variables], [defaultValue] ) - Get a property from an object in any scope

Please refer to our section to take advantage of all the mocking and stubbing you can do. However, every BDD TestBundle has the following functions available to you for mocking and stubbing purposes:

MockBox

Setup and Teardown

TestBox not only provides you with global life-cycle methods but also with localized test methods. This is a great way to keep your tests DRY (Do not repeat yourself)! TestBox provides the setup( currentMethod ) and teardown( currentMethod ) methods that each receives the name of the test method in question. which as their names indicate, they execute before a test and after a test in a test bundle.

component displayName="TestBox xUnit suite" labels="railo,cf"{

     function setup( currentMethod ){
          application.wirebox = new coldbox.system.ioc.Injector();
          structClear( request );
     }

     function teardown( currentMethod ){
          structDelete( application, "wirebox" );
          structClear( request );
     }

     function testThrows(){
          $assert.throws(function(){
               var hello = application.wirebox.getInstance( "myINvalidService" ).run();
          });
     }

     function testNotThrows(){
          $assert.notThrows(function(){
               var hello = application.wirebox.getInstance( "MyValidService" ).run();;
          });
     }

}

Testing Styles

BDD

BDD stands for behavior driven development and is highly based on creating specifications and expectations of results in a readable DSL (Domain Specifc Language).

xUnit

xUnit style of testing is the more traditional TDD or test driven development approach where you are creating a test case CFC that matches the software under test and for each method in the SUT you create a test method in the test case CFC. We cover these two topics in our primers chapter (Chapter 3).

No matter what testing style you pick you, TestBox will execute the tests for you and then send your results to a reporter that can produce awesome testing reports for you.

TestBox comes with two flavors of testing: and .

BDD
xUnit

Optional Inheritance

At runtime we provide the inheritance via mixins so you don't have to worry about it. However, if you want to declare the inheritance you can do so and this will give you the following benefits:

  • Some IDEs will be able to give you introspection for methods and properties

  • You will be able to use the HTML runner by executing directly the runRemote method on the CFC Bundle

  • Your tests will run faster

component extends="testbox.system.BaseSpec"{
}

Reporters

TestBox comes also with a nice plethora of reporters:

  • ANTJunit : A specific variant of JUnit XML that works with the ANT junitreport task

  • Codexwiki : Produces MediaWiki syntax for usage in Codex Wiki

  • Console : Sends report to console

  • Doc : Builds semantic HTML to produce nice documentation

  • Dot : Builds an awesome dot report

  • JSON : Builds a report into JSON

  • JUnit : Builds a JUnit compliant report

  • Raw : Returns the raw structure representation of the testing results

  • Simple : A basic HTML reporter

  • Text : Back to the 80's with an awesome text report

  • XML : Builds yet another XML testing report

  • Tap : A test anything protocol reporter

  • Min : A minimalistic view of your test reports

  • MinText : A minimalistic view of your test reports for consoles

Test Bundles

No matter what style you decide to use, you will still end up building a Testing Bundle CFC. This CFC will either contain BDD style suites and specs or xUnit style method tests. These components are simple components with no inheritance that can contain several different life-cycle callback methods. Internally we do the magic of making this CFC inherit all capabilities and methods from our BaseSpec class (testbox.system.BaseSpec).

Caution We highly encourage you to use the inheritance approach so you can get introspection, faster execution and ability to execute CFC directly.

NodeJS : User-contributed:

https://www.npmjs.com/package/testbox-runner
component{
     // easy right?
}

component extends="testbox.system.BaseSpec"{
     // easy right?
}

Running Tests

TestBox ships with several test runners internally but we have tried to simplify and abstract it with our TestBox object which can be found in the testbox.system package. The TestBox object allows you to execute tests from a CFC, CFM, HTTP, NodeJS, SOAP or REST. You can also make your CFC's extend from our BaseSpec class so you can execute it directly via the URL. The main execution methods are:

// Run tests and produce reporter results
testbox.run()

// Run tests and get raw testbox.system.TestResults object
testbox.runRaw()

// Run tests and produce reporter results from SOAP, REST, HTTP
testbox.runRemote()

// Run via Spec URL
http://localhost/test/spec.cfc?method=runRemote

Info We encourage you to read the API docs included in the distribution for the complete parameters for each method.

run() Arguments

Here are the arguments you can use for initializing TestBox or executing the run() method

Argument

Required

Default

Type

Description

bundles

true

---

string/array

The path, list of paths or array of paths of the spec bundle CFCs to run and test

directory

false

---

struct

The directory mapping path or a struct: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the CFC found, it must return true to process or false to continue process ]

reporter

false

simple

string/struct/instance

The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or an instance of a coldbox.system.reports.IReporter. You can also pass a struct with [type="string or classpath", options={}] if a reporter expects options.

labels

false

false

string/array

The string or array of labels the runner will use to execute suites and specs with.

excludes

false

---

string/array

The string or array of labels to exclude from running

options

false

{}

struct

A structure of property name-value pairs that each runner can implement and use at its discretion.

testBundles

false

---

string/array

A list or array of bundle names that are the ones that will be executed ONLY!

testSuites

false

---

string/array

A list or array of suite names that are the ones that will be executed ONLY!

testSpecs

false

---

string/array

A list or array of test names that are the ones that will be executed ONLY

runRemote() arguments

Here are the arguments you can use for executing the runRemote() method:

Argument

Required

Default

Type

Description

bundles

true

---

string

The path, list of paths or array of paths of the spec bundle CFCs to run and test

directory

false

---

string

The directory mapping to test: directory = the path to the directory using dot notation (myapp.testing.specs)

recurse

false

true

boolean

Recurse the directory mapping or not, by default it does

reporter

false

simple

string/path

The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or a class path to the reporter to use.

reporterOptions

false

{}

JSON

A JSON struct literal of options to pass into the reporter

labels

false

false

string

The string array of labels the runner will use to execute suites and specs with.

excludes

false

---

string/array

The string or array of labels to exclude from running

options

false

{}

JSON

A JSON struct literal of configuration options that are optionally used to configure a runner.

testBundles

false

---

string/array

A list or array of bundle names that are the ones that will be executed ONLY!

testSuites

false

---

string

A list of suite names that are the ones that will be executed ONLY!

testSpecs

false

---

string

A list of test names that are the ones that will be executed ONLY

The bundles argument which can be a single CFC path or an array of CFC paths or a directory argument so it can go and discover the test bundles from that directory. The reporter argument can be a core reporter name like: json,xml,junit,raw,simple,dots,tap,min,etc or it can be an instance of a reporter CFC. You can execute the runners from any cfm template or any CFC or any URL, that is up to you.

Global Runner

TestBox ships with a global runner that can be used to run pretty much anything. You can customize it or place it wherever you need it:

Test Browser

TestBox ships with a test browser that is highly configurable to whatever URL accessible path you want. It will then show you a test browser where you can navigate and execute not only individual tests, but also directory suites as well.

ANT Runner

In our test samples and templates we include an ANT runner that will be able to execute your tests via ANT. It can also leverage our ANTJunit reporter to use the junitreport task to produce JUnit compliant reports as well. You can find this runner in the test samples and runner template directory.

Bundle(s) Runner

<cfset r = new coldbox.system.TestBox( "coldbox.testing.cases.testing.specs.BDDTest" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", testSpecs="OnlyThis,AndThis,AndThis" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", testSuites="Custom Matchers,A Spec" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( [ "coldbox.testing.cases.testing.specs.BDDTest", "coldbox.testing.cases.testing.specs.BDD2Test" ] ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", labels="railo" ) >
<cfoutput>#r.run(reporter="json")#</cfoutput>

Test Runner

Directory Runner

<cfset r = new coldbox.system.TestBox( directory="coldbox.testing.cases.testing.specs" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( directory={ mapping="coldbox.testing.cases.testing.specs", recurse=false } ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox(
      directory={ mapping="coldbox.testing.cases.testing.specs",
      recurse=true,
      filter=function(path){
            return ( findNoCase( "test", arguments.path ) ? true : false );
      }}) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox(
      directory={ mapping="coldbox.testing.cases.testing.specs",
      recurse=true,
      filter=function(path){
            return ( findNoCase( "test", arguments.path ) ? true : false );
      }}) >
<cfset fileWrite( 'testreports.json', r.run() )>

SOAP Runner

You can run tests via SOAP by leveraging the runRemote() method. The WSDL URL will be

http://localhost/testbox/system/TestBox.cfc?wsdl

HTTP/REST Runner You can run tests via HTTP/REST by leveraging the runRemote() endpoint. The URL will be

http://localhost/testbox/system/TestBox.cfc

NodeJS Runner

There is a user-contributed NodeJS Runner that looks fantastic and can be downloaded here:

Just use node to install:

npm install -g testbox-runner

If you make your test bundle CFC inherit from our testbox.system.BaseSpec class, you will be able to execute the CFC directly via the URL: ```javascript ``` You can also pass the following arguments to the method via the URL: * **testSuites** : A list or array of suite names that are the ones that will be executed ONLY! * **testSpecs** : A list or array of test names that are the ones that will be executed ONLY! * **reporter** : The type of reporter to run the test with ```javascript ```

http://localhost/test/MyTest.cfc?method=runRemote
http://localhost/test/MyTest.cfc?method=runRemote&reporter=json
https://www.npmjs.com/package/testbox-runner

Injected Variables

At runtime, TestBox will inject several public variables into your testing bundle CFCs to help you with your testing:

  • $mockbox : A reference to MockBox

  • $assert : A reference to our Assertions library

  • $utility : A utility CFC

  • $customMatchers : A collection of custom matchers registered

  • $exceptionAnnotation : The annotation used to discover expected exceptions, defaults to expectedException

  • $testID : A unique identifier for the test bundle

  • $debugBuffer : The debugging buffer stream

Life-Cycle Methods

Each style has its own life-cycle methods you can discover in their primers. They are callbacks that TestBox calls at specific points in time of testing. They are a great way to setup or teardown things before the entire testing suite or one test case. Here is a short synopsis for each style.

xUnit

  • beforeTests() - Executes once before all tests for the entire test bundle CFC

  • afterTests() - Executes once after all tests complete in the test bundle CFC

  • setup( currentMethod ) - Executes before every single test case and receives the name of the actual testing method

  • teardown( currentMethod ) - Executes after every single test case and receives the name of the actual testing method

component{
     function beforeTests(){}
     function afterTests(){}

     function setup( currentMethod ){}
     function teardown( currentMethod ){}
}

BDD

  • beforeAll() - Executes once before all specs for the entire test bundle CFC

  • afterAll() - Executes once after all specs complete in the test bundle CFC

  • run( testResults, TestBox ) - Executes once so it can capture all your describe and it blocks

  • beforeEach( body, data ) - Executes before every single spec in a single describe block and receives the currently executing spec.

  • afterEach( body, data ) - Executes after every single spec in a single describe block and receives the currently executing spec.

  • aroundEach( body, data ) - Executes around the executing spec so you can provide code surrouding the spec.

component{
     function beforeAll(){}
     function afterAll(){}

     function run( testResults, testBox ){
          describe("A Spec", function(){

               beforeEach( function( currentSpec ){
                    // before each spec in this suite
               });

               afterEach( function( currentSpec ){
                    // after each spec in this suite
               });

               aroundEach( function( spec, suite ){
                    // execute the spec
                    arguments.spec.body();
               });

               describe("A nested suite", function(){

                    // my parent's aroundEach()

                    beforeEach( function(){
                         // before each spec in this suite + my parent's beforeEach()
                    });

                    afterEach( function(){
                         // after each spec in this suite + my parent's afterEach()
                    });

                });

          });

          describe("A second spec", function(){

               beforeEach( function( currentSpec ){
                    // before each spec in this suite, separate from the two other ones
               });

               afterEach( function( currentSpec ){
                    // after each spec in this suite, separate from the two other ones
               });

          });
     }
}

The great flexibility of the BDD approach is that it allows you to nest describe blocks or create multiple describe blocks. Each describe block can have its own life-cycle methods as well. Not only that, if they are nested, TestBox will walk the tree and call each beforeEach() and afterEach() in the order you declare them.

Life-Cycle Data Binding

You can pass in an argument called data which is a struct of dynamic data to pass into the life-cycle method. You can then pickup this data in the closure for the life-cycle.

beforeEach( 
    data = { mydata="luis" }, 
    body = function( currentSpec, data ){
        // The arguments.data is binded via the `data` snapshot above.
        data.myData == "luis";
    }
);

Here is a typical example:

describe( "Ability to bind data to life-cycle methods", function(){

    var data = [
        "spec1",
        "spec2"
    ];

    for( var thisData in data ){
        describe( "Trying #thisData#", function(){

            beforeEach( data={ myData = thisData }, body=function( currentSpec, data ){
                targetData = arguments.data.myData;
            });

            it( title="should account for life-cycle data binding", 
                data={ myData = thisData},
                body=function( data ){
                    expect(    targetData ).toBe( data.mydata );
                }
            );

            afterEach( data={ myData = thisData }, body=function( currentSpec, data ){
                targetData = arguments.data.myData;
            });
        });
    }

    for( var thisData in data ){

        describe( "Trying around life-cycles with #thisData#", function(){

            aroundEach( data={ myData = thisData }, body = function( spec, suite, data ){
                targetData = arguments.data.myData;
                arguments.spec.body( data=arguments.spec.data );
            });

            it( title="should account for life-cycle data binding", 
                data={ myData = thisData },
                body=function( data ){
                    expect(    targetData ).toBe( data.mydata );
                }
            );

        });

    }
});

Annotations

In addition to the life-cycle methods, you can make any method a life-cycle method by giving it the desired annotation in its function definition. This is especially useful for parent classes that want to hook in to the TestBox life-cycle.

  • @beforeAll - Executes once before all specs for the entire test bundle CFC

  • @afterAll - Executes once after all specs complete in the test bundle CFC

  • @beforeEach - Executes before every single spec in a single describe block and receives the currently executing spec.

  • @afterEach - Executes after every single spec in a single describe block and receives the currently executing spec.

  • @aroundEach - Executes around the executing spec so you can provide code surrounding the spec.

Below are several examples using script notation.

DBTestCase.cfc (parent class)

component extends="coldbox.system.testing.BaseTestCase"{

    /**
     * @aroundEach
     */
    function wrapInDBTransaction( spec, suite ){
        transaction action="begin" {
            try {
                arguments.spec.body();
            } catch (any e) {
                rethrow;
            } finally {
                transaction action="rollback"
            }
        }
     }
}

PostsTest.cfc

component extends="DBTestCase"{

    /**
     * @beforeEach
     */
    function setupColdBox() {
        setup();
    }

    function run() {
        given( "I have a two posts", function(){
            when( "I visit the home page", function(){
                then( "There should be two posts on the page", function(){
                    queryExecute( "INSERT INTO posts (body) VALUES ('Test Post One')" );
                    queryExecute( "INSERT INTO posts (body) VALUES ('Test Post Two')" );

                    var event = execute( event = "main.index", renderResults = true );

                    var content = event.getCollection().cbox_rendered_content;

                    expect(content).toMatch( "Test Post One" );
                    expect(content).toMatch( "Test Post Two" );
                });
            });
        });
    }
}

This also helps parent classes enforce their setup methods are called by annotating the methods with @beforeAll. No more forgetting to call super.beforeAll()!

Info: You can have as many annotated methods as you would like. TestBox discovers them up the inheritance chain and calls them in reverse order.

Injected Methods

// quick assertion methods
assert( expression, [message] )
fail(message)

// bdd methods
beforeEach( body )
afterEach( body )
describe( title, body, labels, asyncAll, skip )
xdescribe( title, body, labels, asyncAll )
it( title, body, labels, skip )
xit( title, body, labels )
expect( actual )

// extensions
addMatchers( matchers )
addAssertions( assertions )

// runners
runRemote( testSpecs, testSuites, deubg, reporter );

// utility methods
console( var, top )
debug( var, deepCopy, label, top )
clearDebugBuffer()
getDebugBuffer()
print( message )
printLn( message )

// mocking methods
makePublic( target, method, newName )
querySim( queryData )
getmockBox( generationPath )
createEmptyMock( className, object, callLogging )
createMock( className, object, clearMethods, callLogging )
prepareMock( object, callLogging )
createStub( callLogging, extends, implements )
getProperty( target, name, scope, defaultValue )

At runtime, TestBox will also decorate your bundle CFC with tons of methods to help you in your testing adventures. Depending on your testing style you will leverage some more than others. For the latest methods please visit the for more information.

API Docs

xUnit

The testing bundle CFC is actually the suite in xUnit style as it contains all the test methods you would like to test with. Usually, this CFC represents a test case for a specific software under test (SUT), whether that's a model object, service, etc. This component can have some cool annotations as well that can alter its behavior.

component displayName="The name of my suite" asyncAll="boolean" labels="list" skip="boolean"{

}

Bundle Annotations

Argument

Required

Default

Type

Description

displayName

false

--

string

If used, this will be the name of the test suite in the reporters.

asyncAll

false

false

boolean

If true, it will execute all the test methods in parallel and join at the end asynchronously.

labels

false

---

string/list

The list of labels this test belongs to

skip

false

false

boolean/udf

A boolean flag that makes the runners skip the test for execution. It can also be the name of a UDF in the same CFC that will be executed and MUST return a boolean value.

Caution If you activate the asyncAll flag for asynchronous testing, you HAVE to make sure your tests are also thread safe and appropriately locked.

Tests

TestBox discovers test methods in your bundle CFC by applying the following discovery rules:

  • Any method that has a test annotation on it

  • Any public method that starts or ends with the word test

// Via inline annotation
function shouldBeAwesome() test{}

/**
* Via comment annotation
* @test
*/
function shouldBeAwesome(){}

// via conventions
function testShouldDoThis(){}
function shouldDoThisTest(){}
function testIncludes(){
       $assert.includes( "hello", "HE" );
       $assert.includes( [ "Monday", "Tuesday" ] , "monday" );
}

Each test function can also have some cool annotations attached to it.

Arguments

Required

Default

Type

Description

labels

false

string/list

---

The list of labels this test belongs to

skip

false

false

boolean/udf

A boolean flag that makes the runners skip the test for execution. It can also be the name of a UDF in the same CFC that will be executed and MUST return a boolean value.

Each test method will test the state of the SUT (software under test) or sometimes referred to as code under test. It will do so by asserting that actual values from an execution match an expected value or condition. TestBox offers an assertion library that you have available in your bundle via the injected variable $assert. You can also use our expectations library if you so desire, but that is mostly used in our BDD approach (chapter 3: ).

Primers

Suites, Tests & Specs (Oh My!)

Test suites are a collection of testing methods or specifications (specs) inside of a testing bundle CFC. Let's investigate the styles.

BDD

A test suite begins with a call to our TestBox describe() function with at least two arguments: a title and a body closure within the life-cycle method called run(). The title is the name of the suite to register and the body function is the block of code that implements the suite. There are more arguments which you can see below:

Argument

Required

Default

Type

Description

title

true

---

string

The title of the suite to register

body

true

---

closure/udf

The closure that represents the test suite

labels

true

---

string/array

The list or array of labels this suite group belongs to

asyncAll

false

false

Boolean

If you want to parallelize the execution of the defined specs in this suite group.

skip

false

false

Boolean

A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean.

function run( testResults, testBox ){

     describe("A suite", function(){
          it("contains spec with an awesome expectation", function(){
               expect( true ).toBeTrue();
          });
     });

}

In BDD, suites can be nested within each other which provides a great capability of building trees of tests. Not only does it arrange them in tree format but also TestBox will execute the life-cycle methods in order of nested suites as it traverses the tree.

describe("A spec", function() {

     beforeEach(function() {
          coldbox = 22;
          application.wirebox = new coldbox.system.ioc.Injector();
     });

     afterEach(function() {
          coldbox = 0;
          structDelete( application, "wirebox" );
     });

     it("is just a function, so it can contain any code", function() {
          expect( coldbox ).toBe( 22 );
     });

     it("can have more than one expectation and talk to scopes", function() {
          expect( coldbox ).toBe( 22 );
          expect( application.wirebox.getInstance( 'MyService' ) ).toBeComponent();
     });

     describe("nested inside a second describe", function() {

          beforeEach(function() {
               awesome = 22;
          });

          afterEach(function() {
               awesome = 22 + 8;
          });

          it("can reference both scopes as needed ", function() {
            expect( coldbox ).toBe( awesome );
          });
     });

     it("can be declared after nested suites and have access to nested variables", function() {
          expect( awesome ).toBe( 30 );
     });

});

BDD Specs

Specs are defined by calling the TestBox it() global function, which takes in a title and a function. The title is the title of this spec or test you will write and the function is a block of code that represents the test/spec. A spec will contain most likely one or more expectations that will test the state of the SUT (software under test) or sometimes referred to as code under test.

Argument

Required

Default

Type

Description

title

true

---

string

The title of the spec

body

true

---

closure/udf

The closure that represents the spec

labels

false

---

string/array

The list or array of labels this suite group belongs to

skip

false

false

Boolean

A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean.

data

false

{}

struct

A struct of data you can bind the spec with so you can use within the body closure

An expectation is a nice assertion DSL that TestBox exposes so you can pretty much read what should happen in the testing scenario. A spec will pass if all expectations pass. A spec with one or more expectations that fail will fail the entire spec.

function run( testResults, testBox ){

     describe("A suite", function(){
          it("contains spec with an awesome expectation", function(){
               expect( true ).toBeTrue();
          });

          it("contains a spec with more than 1 expectation", function(){
               expect( [1,2,3] ).toBeArray();
               expect( [1,2,3] ).toHaveLength( 3 );
          });
     });

}

Assertions

fail( [message] )
assert( expression, [message] )
isTrue( actual, [message] )
isFalse( actual, [message] )
isEqual(expected, actual, [message])
isNotEqual(expected, actual, [message])
isEqualWithCase(expected, actual, [message])
closeTo(expected, actual, delta, [datePart], [message])
null( actual, [message] )
notNull( actual, [message] )
typeOf( type, actual, [message] )
notTypeOf( type, actual, [message] )
instanceOf( actual, typeName, [message] )
notInstanceOf( actual, typeName, [message] )
match( actual, regex, [message] )
matchWithCase( actual, regex, [message] )
notMatch( actual, regex, [message] )
key( target, key, [message] )
notKey( target, key, [message] )
deepKey( target, key, [message] )
notDeepKey( target, key, [message] )
lengthOf( target, length, [message] )
notLengthOf( target, length, [message] )
isEmpty( target, [message] )
isNotEmpty( target, [message] )
throws(target, [type], [regex], [message])
notThrows(target, [type], [regex], [message])
between( actual, min, max, [message] )
includes( target, needle, [message] )
notIncludes( target, needle, [message] )
includesWithCase( target, needle, [message] )
notIncludesWithCase( target, needle, [message] )
isGT( actual, target, [message])
isGTE( actual, target, [message])
isLT( actual, target, [message])
isLTE( actual, target, [message])

Custom Assertions

TestBox comes with a plethora of assertions that cover what we believe are common scenarios. However, we recommend that you create custom assertions that meet your needs and criteria so that you can avoid duplication and have re-usability. A custom assertion function can receive any amount of arguments but it must use the fail() method in order to fail an assertion or just return true or void for passing.

Here is an example:

Assertion Registration

You can register assertion functions in several ways within TestBox, but we always recommend that you register them inside of the beforeTests() or setup() life-cycle method blocks, so they are only inserted once.

Inline Assertions

You can pass a structure of key/value pairs of the assertions you would like to register. The key is the name of the assertion function and the value is the closure function representation.

After it is registered, then you can just use it out of the $assert object it got mixed into.

CFC Assertions

You can also register more than 1 CFC by using a list or an array:

Here is the custom assertions CFC source:

TestBox supports the concept of to allow for validations and for legacy tests. We encourage developers to use our BDD expectations as they are more readable and fun to use (Yes, fun I said!).

The assertions are modeled in the class testbox.system.Assertion, so you can visit the for the latest assertions available. Each test bundle will receive a variable called $assert which represents the assertions object. Here are some common assertion methods:

You can also store a of assertions (Yes, I said plethora), in a CFC and register that as the assertions via its instantiation path. This provides much more flexibility and re-usability for your projects.

assertions
API
function isAwesome( required expected ){
     return ( arguments.expected == "TestBox" ? fail( 'TestBox is always awesome' ) : true );
}
function beforeTests(){

     addAssertions({
          isAwesome = function( required expected ){
               return ( arguments.expected == "TestBox" ? true : fail( 'not TestBox' ) );
          },
          isNotAwesome = function( required expected ){
               return ( arguments.expected == "TestBox" ? fail( 'TestBox is always awesome' ) : true );
          }
     });

}
function testAwesomenewss(){
  $assert.isAwesome( 'TestBox' );
}
addAssertions( "model.util.MyAssertions" );
addAssertions( "model.util.MyAssertions, model.util.RegexAssertions" );
addAssertions( [ "model.util.MyAssertions" , "model.util.RegexAssertions" ] );
component{

     function assertIsAwesome( expected, actual ){
          return ( expected eq actual ? true : false );
     }

     function assertIsFunky( actual ){
          return ( actual gte 100 ? true : false );
     }

}

Expectations

TestBox allows you to create BDD expectations with our expectations and matcher API DSL. You start by calling our expect() method, usually with an actual value you would like to test. You then concatenate the expectation of that actual value/function to a result or what we call a matcher. You can also concatenate matchers (as of v2.1.0) so you can provide multiple matching expectations to a single value.

expect( 43 ).toBe( 42 );
expect( function(){ calculator.add(2,2); } ).toThrow();
plethora

Not Operator

expect( actual )
     .notToBe( 4 )
     .notToBeTrue();
     .notToBeFalse();

You can prefix your expectation with the not operator to easily cause negative expectations for any matcher. When you read the or the source, you will find nowhere the not methods. This is because we do this dynamically by convention.

API Docs

Custom Matchers

TestBox comes with a decent amount of matchers that cover what we believe are common scenarios. However, we recommend that you create custom matchers that meet your needs and criteria so that you can avoid duplication and have re-usability.

Every custom matcher is a function and must have the following signature, with MyMatcher being the name of your custom matcher function:

boolean function MyMatcher( required expectation, args={} )

The matcher function receives the expectation object and a second argument which is a structure of all the arguments with which the matcher function was called with. It must then return a true or a false depending if it passes your criteria. It will most likely use the expectation object to retrieve the actual and isNot values. It can also set a custom failure message on the expectation object itself by using the message property of the expectation object.

boolean function reallyFalse( expectation, args={} ){
     expectation.message = ( structKeyExists( args, "message" ) ? args.message : "[#expectation.actual#] is not really false" );
     if( expectation.isNot )
          return ( expectation.actual eq true );
     else
          return ( expectation.actual eq false );
     }
}

The next step is to tell TestBox about your matcher.

Matcher Registration

You can register matcher functions in several ways within TestBox, but we always recommend that you register them inside of the beforeAll() or beforeEach() life-cycle method blocks for performance considerations and global availability.

Inline matchers

You can pass a structure of key\/value pairs of the matchers you would like to register. The key is the name of the matcher function and the value is the closure function representation.

function beforeAll(){

  addMatchers( {
       toBeAwesome : function( expectation, args={} ){ return expectation.actual gte 100; },
       toBeGreat : function( expectation, args={} ){ return expectation.actual gte 1000; },
       // please note I use positional values here, you can also use name-value arguements.
       toBeGreaterThan : function( expectation, args={} ){ return ( expectation.actual gt args[ 1 ]  ); }
  } );

}

After it is registered, then you can use it.

it("A custom matcher", function(){
  expect( 100 ).toBeAwesome();
  expect( 5000 ).toBeGreat();
  expect( 10 ).toBeGreaterThan( 5 );
});

CFC Matchers

addMatchers( "model.util.MyMatchers" );

You can also register a CFC instance:

addMatchers( new models.util.MyMatchers() );

Matchers

toBeTrue( [message] ) :  value to true
toBeFalse( [message] ) : value to be false
toBe( expected, [message] ) : Assert something is equal to each other, no case is required
toBeWithCase( expected, [message] ) : Expects with case
toBeNull( [message] ) : Expects the value to be null
toBeInstanceOf( class, [message] ) : To be the class instance passed
toMatch( regex, [message] ) : Matches a string with no case-sensitivity
toMatchWithCase( regex, [message] ) : Matches with case-sensitivity
toBeTypeOf( type, [message] ) : Assert the type of the incoming actual data, it uses the internal ColdFusion isValid() function behind the scenes, type can be array, binary, boolean, component, date, time, float, numeric, integer, query, string, struct, url, uuid plus all the ones from isValid()
toBe{type}( [message] ) : Same as above but more readable method name. Example: .toBeStruct(), .toBeArray()
toBeEmpty( [message] ) : Tests if an array or struct or string or query is empty
toHaveKey( key, [message] ) : Tests the existence of one key in a structure or hash map
toHaveDeepKey( key, [message] ) : Assert that a given key exists in the passed in struct by searching the entire nested structure
toHaveLength( length, [message] ) : Assert the size of a given string, array, structure or query
toThrow( [type], [regex], [message] );
toBeCloseTo( expected, delta, [datepart], [message] ) : Can be used to approximate numbers or dates according to the expected and delta arguments.  For date ranges use the datepart values.
toBeBetween( min, max, [message] ) : Assert that the passed in actual number or date is between the passed in min and max values
toInclude( needle, [message] ) : Assert that the given "needle" argument exists in the incoming string or array with no case-sensitivity, needle in a haystack anyone?
toIncludeWithCase( needle, [message] ) : Assert that the given "needle" argument exists in the incoming string or array with case-sensitivity, needle in a haystack anyone?
toBeGT( target, [message] ) : Assert that the actual value is greater than the target value
toBeGTE( target, [message] ) : Assert that the actual value is greater than or equal the target value
toBeLT( target, [message] ) : Assert that the actual value is less than the target value
toBeLTE( target, [message] ) : Assert that the actual value is less than or equal the target value

You can also store a of matchers (Yes, I said plethora), in a CFC and register that as the matchers via its instantiation path. This provides much more flexibility and re-usability for your projects.

The toBe() matcher represents an equality matcher much how an $assert.isEqual() behaves. Below are several of the most common matchers available to you. However, the best way to see which ones are available is to checkout the .

plethora
API Docs

Output Utilities

Sometimes you will need to produce output from your tests and you can do so elegantly via some functions we have provided that are available in your test bundles:

  • console(var, top=[9999]) - Send data to the console via writeDump( output="console" )

  • debug(var, label="", deepCopy=[false], top=999) - Send data to the TestBox debug stream, will be saved in the test results and outputted in reports

  • print( message ) - Write some output to the ColdFusion output buffer

  • println( message ) - Write some output to the ColdFusion output buffer and appends a newline character

These are great little utilities that are needed to send output to several locations.

Hint Please note that the debug() method does NOT do deep copies by default.

console( myResults );

debug( myData );
debug( myData, true );

debug( var=myData, top=5 );

print( "Running This Test with #params.toString()#" );
println( "Running This Test with #params.toString()#" );

Expecting Exceptions

Our default syntax for expecting exceptions is to use our closure approach concatenated with our toThrow() method in our expectations or our throws() method in our assertions object.

Info Please always remember to pass in a closure to these methods and not the actual test call: function(){ myObj.method();}

Example

expect( function(){ myObj.method(); } ).toThrow( [type], [regex], [message] );
$assert.throws( function(){ myObj.method; }, [type], [regex], [message] )

This will execute the closure in a nested try/catch block and make sure that it either threw an exception, threw with a type, threw with a type and a regex match of the exception message. If you are in an environment that does not support closures then you will need to create a spec testing function that either uses the expectedException annotation or function call:

function testMyObj(){
     expectedException( [type], [regex], [message] );
}

function testMyObj() expectedException="[type]:[regex]"{
     // this function should produce an exception
}

Caution Please note that the usage of the expectedException() method can ONLY be used while in synchronous mode. If you are running your tests in asynchronous mode, this will not work. We would recommend the closure or annotation approach instead.

Test Browser

TestBox ships with a test browser that is highly configurable to whatever URL accessible path you want. It will then show you a test browser where you can navigate and execute not only individual tests, but also directory suites as well.

Run Listeners

Every run and runRaw methods now accept a callbacks argument, which can be a CFC with the right listener methods or a struct with the right closure methods. This will allow you to listen to the testing progress and get information about it. This way you can build informative reports or progress bars.

The available callbacks are:

// Called at the beginning of a test bundle cycle
function onBundleStart( target, testResults )
// Called at the end of the bundle testing cycle
function onBundleEnd( target, testResults )

// Called anytime a new suite is about to be tested
function onSuiteStart( target, testResults, suite )
// Called after any suite has finalized testing
function onSuiteEnd( target, testResults, suite )

// Called anytime a new spec is about to be tested
function onSpecStart( target, testResults, suite, spec )
// Called after any spec has finalized testing
function onSpecEnd( target, testResults, suite, spec )

Running Tests

TestBox ships with several test runners internally but we have tried to simplify and abstract it with our TestBox object which can be found in the testbox.system package. The testbox object allows you to execute tests from a CFC, CFM, HTTP, SOAP, NodeJS or REST. You can also make your CFC's extend from our BaseSpec class so you can execute it directly via the URL. The main execution methods are:

// Run tests and produce reporter results
testbox.run()

// Run tests and get raw testbox.system.TestResults object
testbox.runRaw()

// Run tests and produce reporter results from SOAP, REST, HTTP
testbox.runRemote()

// Run via Spec URL
http://localhost/tests/spec.cfc?method=runRemote

run() Arguments

Here are the arguments you can use for initializing TestBox or executing the run() method

Argument

Required

Default

Type

Description

bundles

true

---

string/array

The path, list of paths or array of paths of the spec bundle CFCs to run and test

directory

false

---

struct

The directory mapping path or a struct: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the CFC found, it must return true to process or false to continue process ]

reporter

false

simple

string/struct/instance

The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or an instance of a coldbox.system.reports.IReporter. You can also pass a struct with [type="string or classpath", options={}] if a reporter expects options.

labels

false

false

string/array

The string or array of labels the runner will use to execute suites and specs with.

excludes

false

---

string/array

The string or array of labels to exclude from running

options

false

{}

struct

A structure of property name-value pairs that each runner can implement and use at its discretion.

testBundles

false

---

string/array

A list or array of bundle names that are the ones that will be executed ONLY!

testSuites

false

---

string/array

A list or array of suite names that are the ones that will be executed ONLY!

testSpecs

false

---

string/array

A list or array of test names that are the ones that will be executed ONLY

callbacks

false

{}

struct of closures or CFC

A struct of listener callbacks or a CFC with callbacks for listening to progress of the testing: onBundleStart,onBundleEnd,onSuiteStart,onSuiteEnd,onSpecStart,onSpecEnd

eagerFailure

false

false

boolean

If true, then after testing a bundle if there are any failures or errors no more testing will be performed.

runRemote() Arguments

Here are the arguments you can use for executing the runRemote() method of the TestBox object:

Argument

Required

Default

Type

Description

bundles

true

---

string

The path, list of paths or array of paths of the spec bundle CFCs to run and test

directory

false

---

string

The directory mapping to test: directory = the path to the directory using dot notation (myapp.testing.specs)

recurse

false

true

boolean

Recurse the directory mapping or not, by default it does

reporter

false

simple

string/path

The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or a class path to the reporter to use.

reporterOptions

false

{}

JSON

A JSON struct literal of options to pass into the reporter

labels

false

false

string

The string array of labels the runner will use to execute suites and specs with.

excludes

false

---

string/array

The string or array of labels to exclude from running

options

false

{}

JSON

A JSON struct literal of configuration options that are optionally used to configure a runner.

testBundles

false

---

string/array

A list or array of bundle names that are the ones that will be executed ONLY!

testSuites

false

---

string

A list of suite names that are the ones that will be executed ONLY!

testSpecs

false

---

string

A list of test names that are the ones that will be executed ONLY

eagerFailure

false

false

boolean

If true, then after testing a bundle if there are any failures or errors no more testing will be performed.

Notes

  • The bundles argument which can be a single CFC path or an array of CFC paths or a directory argument so it can go and discover the test bundles from that directory.

  • The reporter argument can be a core reporter name like: json,xml,junit,raw,simple,dots,tap,min,etc or it can be an instance of a reporter CFC.

  • You can execute the runners from any cfm template or any CFC or any URL, that is up to you.

We encourage you to read the included in the distribution for the complete parameters for each method.

API docs

Bundle(s) Runner

<cfset r = new coldbox.system.TestBox( "coldbox.testing.cases.testing.specs.BDDTest" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", testSpecs="OnlyThis,AndThis,AndThis" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", testSuites="Custom Matchers,A Spec" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( [ "coldbox.testing.cases.testing.specs.BDDTest", "coldbox.testing.cases.testing.specs.BDD2Test" ] ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( bundles="coldbox.testing.cases.testing.specs.BDDTest", labels="railo" ) >
<cfoutput>#r.run(reporter="json")#</cfoutput>

Global Runner

TestBox ships with a global runner that can be used to run pretty much anything. You can customize it or place it wherever you need it:

Test Runner

If you make your test bundle CFC inherit from our testbox.system.BaseSpec class, you will be able to execute the CFC directly via the URL:

You can also pass the following arguments to the method via the URL:

  • testSuites : A list or array of suite names that are the ones that will be executed ONLY!

  • testSpecs : A list or array of test names that are the ones that will be executed ONLY!

  • reporter : The type of reporter to run the test with

http://localhost/test/MyTest.cfc?method=runRemote
http://localhost/test/MyTest.cfc?method=runRemote&reporter=json

SOAP Runner

You can run tests via SOAP by leveraging the runRemote() method. The WSDL URL will be

http://localhost/testbox/system/TestBox.cfc?wsdl

HTTP REST Runner

You can run tests via HTTP/REST by leveraging the runRemote() endpoint. The URL will be

http://localhost/testbox/system/TestBox.cfc?method=runRemote

Directory Runner

<cfset r = new coldbox.system.TestBox( directory="coldbox.testing.cases.testing.specs" ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox( directory={ mapping="coldbox.testing.cases.testing.specs", recurse=false } ) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox(
      directory={ mapping="coldbox.testing.cases.testing.specs",
      recurse=true,
      filter=function(path){
            return ( findNoCase( "test", arguments.path ) ? true : false );
      }}) >
<cfoutput>#r.run()#</cfoutput>

<cfset r = new coldbox.system.TestBox(
      directory={ mapping="coldbox.testing.cases.testing.specs",
      recurse=true,
      filter=function(path){
            return ( findNoCase( "test", arguments.path ) ? true : false );
      }}) >
<cfset fileWrite( 'testreports.json', r.run() )>

NodeJS Runner

You can use node to install as well:

npm install -g testbox-runner

ANT Runner

In our test samples and templates we include an ANT runner that will be able to execute your tests via ANT. It can also leverage our ANTJunit reporter to use the junitreport task to produce JUnit compliant reports as well. You can find this runner in the test samples and runner template directory.

Ant

This build is our global build for running TestBox via ANT.

There is a user-contributed NodeJS Runner that looks fantastic and can be downloaded here:

https://www.npmjs.com/package/testbox-runner
<cfsetting showDebugOutput="false">
<cfparam name="url.reporter"       default="simple">
<cfparam name="url.directory"      default="test.specs">
<cfparam name="url.recurse"        default="true" type="boolean">
<cfparam name="url.bundles"        default="">
<cfparam name="url.labels"         default="">
<cfparam name="url.reportpath"     default="#expandPath( "/tests/results" )#">
<cfscript>
// prepare for tests for bundles or directories
if( len( url.bundles ) ){
     testbox = new testbox.system.TestBox( bundles=url.bundles, labels=url.labels );
}
else{
     testbox = new testbox.system.TestBox( directory={ mapping=url.directory, recurse=url.recurse}, labels=url.labels );
}
// Run Tests using correct reporter
results = testbox.run( reporter=url.reporter );
// do stupid JUnitReport task processing, if the report is ANTJunit
if( url.reporter eq "ANTJunit" ){
     xmlReport = xmlParse( results );
     for( thisSuite in xmlReport.testsuites.XMLChildren ){
          fileWrite( url.reportpath & "/TEST-" & thisSuite.XMLAttributes.name & ".xml", toString( thisSuite ) );
     }
}
// Writeout Results
writeoutput( results );
</cfscript>
<?xml version="1.0"?>
<--<
This ANT build can be used to execute your tests with automation using our included runner.cfm.
You can executes directories, bundles and so much more.  It can also produce JUnit reports using the
ANT junitreport tag.  This is meant to be a template for you to spice up.

There are two targets you can use: run and run-junit

Execute the default 'run' target
ant -f test.xml
OR
ant -f test.xml run

Execute the 'run-junit' target
ant -f test.xml run-junit

PLEASE NOTE THAT YOU MUST ALTER THE RUNNER'S URL ACCORDING TO YOUR ENVIRONMENT.
-->
<project name="testbox-ant-runner" default="run" basedir=".">

     <-- <THE URL TO THE RUNNER, PLEASE CHANGE ACCORDINGLY-->
     <property name="url.runner"             value="http://cf10cboxdev.jfetmac/coldbox/ApplicationTemplates/Advanced/tests/runner.cfm?"/>
     <-- <FILL OUT THE BUNDLES TO TEST, CAN BE A LIST OF CFC PATHS -->
     <property name="test.bundles"      value="" />
     <-- <FILL OUT THE DIRECTORY MAPPING TO TEST -->
     <property name="test.directory"         value="test.specs" />
     <-- <FILL OUT IF YOU WANT THE DIRECTORY RUNNER TO RECURSE OR NOT -->
     <property name="test.recurse"      value="true" />
     <-- <FILL OUT THE LABELS YOU WANT TO APPLY TO THE TESTS -->
     <property name="test.labels"       value="" />
     <-- <FILL OUT THE TEST REPORTER YOU WANT, AVAILABLE REPORTERS ARE: ANTJunit, Codexwiki, console, dot, doc, json, junit, min, raw, simple, tap, text, xml -->
     <property name="test.reporter"          value="simple" />
     <-- <FILL OUT WHERE REPORTING RESULTS ARE STORED -->
     <property name="report.dir"        value="${basedir}/results" />
     <property name="junitreport.dir"   value="${report.dir}/junitreport" />

     <target name="init" description="Init the tests">
          <mkdir dir="${junitreport.dir}" />
          <tstamp prefix="start">
               <format property="TODAY" pattern="MM-dd-YYYY hh:mm:ss aa"/>
          </tstamp>
          <concat destfile="${report.dir}/latestrun.log">Tests ran at ${start.TODAY}</concat>
     </target>

     <target name="run" depends="init" description="Run our tests and produce awesome results">

          <-- <Directory Runner
               Executes recursively all tests in the passed directory and stores the results in the
               'dest' param.  So if you want to rename the file, do so here.

                Available params for directory runner:
                - Reporter
                - Directory
                - Recurse
                - Labels
          -->
          <get dest="${report.dir}/results.html"
                src="${url.runner}&directory=${test.directory}&recurse=${test.recurse}&reporter=${test.reporter}&labels=${test.labels}"
                verbose="true"/>

          <-- <Bundles Runner
               You can also run tests for specific bundles by using the runner with the bundles params

               Available params for runner:
                - Reporter
                - Bundles
                - Labels

          <get dest="${report.dir}/results.html"
                src="${url.runner}&bundles=${test.bundles}&reporter=${test.reporter}&labels=${test.labels}"
                verbose="true"/>
           -->

     </target>

     <target name="run-junit" depends="init" description="Run our tests and produce ANT JUnit reports">

          <-- <Directory Runner
               Executes recursively all tests in the passed directory and stores the results in the
               'dest' param.  So if you want to rename the file, do so here.

                Available params for directory runner:
                - Reporter = ANTJunit fixed
                - Directory
                - Recurse
                - Labels
                - ReportPath : The path where reports will be stored, by default it is the ${report.dir} directory.
          -->
          <get dest="${report.dir}/results.xml"
                src="${url.runner}&directory=${test.directory}&recurse=${test.recurse}&reporter=ANTJunit&labels=${test.labels}&reportPath=${report.dir}"
                verbose="true"/>

          <-- <Bundles Runner
               You can also run tests for specific bundles by using the runner with the bundles params

               Available params for runner:
                - Reporter
                - Bundles
                - Labels

          <get dest="${report.dir}/results.html"
                src="${url.runner}&bundles=${test.bundles}&reporter=${test.reporter}&labels=${test.labels}"
                verbose="true"/>
           -->

           <-- <Create fancy junit reports -->
          <junitreport todir="${junitreport.dir}">
               <fileset dir="${report.dir}">
                    <include name="TEST-*.xml"/>
               </fileset>
               <report format="frames" todir="${junitreport.dir}">
                    <param name="TITLE" expression="My Awesome TestBox Results"/>
               </report>
          </junitreport>

     </target>

</project>

System Requirements

MockBox has been designed to work under the following CFML Engines:

  • Railo 3.1+ (Deprecated)

  • Lucee 4.5+

  • Adobe ColdFusion 9+

  • Write capabilities on disk for the default path of /{mockbox}/system/testings/stubs. You can also choose the directory destination for stub creations yourself when you initialize MockBox. If using ColdFusion 9 or Railo you can even use ram:// and use the virtual file system.

MockBox

Introduction

MockBox is a companion package to TestBox that will give you advanced mocking/stubbing capabilities; hence a Mocking Framework. You can use it from within TestBox or as a standalone package in any other testing framework or CFML application as a data provider.

Download

git clone git@github.com:Ortus-Solutions/TestBox.git

Useful Resources

MockBox

Download from our or clone via github. Also read our section.

downloads page
installation
Approaches to Mocking
Wikipedia Mock Objects
Using mock objects for complex unit tests IBM developerWorks
Unit testing with mock objects IBM developerWorks
Emergent Design by Scott Bain
Mocks Aren't Stubs by Martin Fowler

Installing Mockbox

// install standalone
box install testbox

// installing bleeding edge
box install testbox@be

MockBox can be downloaded from or can be installed via as part of the TestBox package.

http://www.ortussolutions.com/products/testbox
CommandBox

MXUnit Compatibility

Note you will still need TestBox to be in the web root, or have a /testbox mapping created even when using the MXUnit compat runner.

this.mappings[ "/mxunit" ] = expandPath( "/testbox/system/compat" );

After this, all your test code remains the same but it will execute through TestBox's xUnit runners. You can even execute them via the normal URL you are used to. If there is something that is not compatible, please let us know and we will fix it.

Expected Exceptions

We also support in the compatibility mode the expected exception MXUnit annotation: mxunit:expectedException and the expectException() methods. The expectException() method is not part of the assertion library, but instead is inherited from our BaseSpec.cfc.

Please refer to MXUnit's documentation on the annotation and method for expected exceptions, but it is supported with one caveat. The expectException() method can produce unwanted results if you are running your test specs in TestBox asynchronous mode since it stores state at the component level. Only synchronous mode is supported if you are using the expectException() method. The annotation can be used in both modes.

Reporters

TestBox comes also with a nice plethora of reporters:

  • ANTJunit : A specific variant of JUnit XML that works with the ANT junitreport task

  • Codexwiki : Produces MediaWiki syntax for usage in Codex Wiki

  • Console : Sends report to console

  • Doc : Builds semantic HTML to produce nice documentation

  • Dot : Builds an awesome dot report

  • JSON : Builds a report into JSON

  • JUnit : Builds a JUnit compliant report

  • Raw : Returns the raw structure representation of the testing results

  • Simple : A basic HTML reporter

  • Text : Back to the 80's with an awesome text report

  • XML : Builds yet another XML testing report

  • Tap : A test anything protocol reporter

  • Min : A minimalistic view of your test reports

To use a specific reporter append the reporter variable to the url string. ex &reporter=Text

However, you can also build custom reporters very easily.

TestBox is fully compliant with xUnit test cases. In order to leverage it you will need to create or override the /mxunit mapping and make it point to the /testbox/system/compat folder. That's it, everything should continue to work as expected.

MXUnit

What is Mocking?

Here are some examples of real life mocks to get you in the mocking mood:

When doing unit testing of ColdFusion CFCs, we will come to a point where a single class can have multiple external dependencies or collaborators; whether they are classes themselves, data, external APIs, etc. Therefore, in order to unit test our class exclusively and easily we need to be able to mock this behavior or have full control of this behavior. Remember that unit testing is the testing of software units in isolation. If not, we would be instantiating and creating entire set of components, frameworks, pulling network plugs and so much more ridiculous but functional things just to test one single piece of functionality and/or behavior. So in summary, mock objects are just test oriented replacements for collaborators and dependencies.

"Mocks are definitely congruent with the Gang of Four (GoF) notion of designing to interfaces, because a mock is essentially the interface without any real implementation." - Scott Bain (Emergent Design)

You will be leveraging MockBox to create objects that represent your dependencies or even data, decide what methods will return (expectations), mock network connections, exceptions and much more. You can then very easily test the exclusive behavior of components as you will now have control of all expectations, and remember that testing is all about expectations. Also, as your object oriented applications get more complex, mocking becomes essential, but you have to be aware that there are limitations. Not only will you do unit-testing but you will need to expand to do integration testing to make sure the all encompassing behavior is still maintained. However, by using a mocking framework like MockBox you will be able to apply a test-driven development methodology to your unit-testing and be able to accelerate your development and testing. The more you mock, the more you will get a feel for it and find it completely essential when doing unit testing. Welcome to a world where mocking is fun and not frowned upon :)

"A mock object is an object that takes the place of a 'real' object in such a way that makes testing easier and more meaningful, or in some cases, possible at all". by Scott Bain (Emergent Design )

Mock objects can also be created by hand, but MockBox takes this pain away by leveraging dynamic techniques so that you can Mock dynamically and at runtime. Like describes in his book:

The Evolutionary Nature of Professional Software Development
Scott Bain
Emergent Design

Custom Reporters

Building Reporters

You can also build your own reporters by implementing our core class: testbox.system.reporters.IReport

interface{

  /**
  * Get the name of the reporter
  */
  function getName();

  /**
  * Do the reporting thing here using the incoming test results
  * The report should return back in whatever format they desire and should set any
  * Specifc browser types if needed.
  * @results.hint The instance of the TestBox TestResult object to build a report on
  * @testbox.hint The TestBox core object
  * @options.hint A structure of options this reporter needs to build the report with
  */
  any function runReport(
    required testbox.system.TestResult results,
    required testbox.system.TestBox testbox,
    struct options={} );
}

Executing Your Reporter

Once you implement your own report you just need to pass the class path or the instance of your reporter to the TestBox runner methods using the reporter argument. The reporter argument can be the following values:

  • string - The class path of your reporter

  • instance - The instance of your reporter CFC

  • struct - A structure representing your reporter with the following keys: { type="class_path", options={}. This is mostly used if you want to instantiate and use your reporter with a structure of options.

Now you can init TestBox with your reporter:

r = new TestBox( reporter="my.path.toCustomReporter" );
r = new TestBox( reporter= new my.path.CustomReporter() );
r = new TestBox( reporter={
    type = "my.path.to.CustomReporter",
    options = { name = value, name2 = value2 }
} );

Sample Reporter

Here is a sample reporter for you that generates JSON for the output.

component{

  function init(){ return this; }

  /**
  * Get the name of the reporter
  */
  function getName(){
    return "JSON";
  }

  /**
  * Do the reporting thing here using the incoming test results
  * The report should return back in whatever format they desire and should set any
  * Specifc browser types if needed.
  * @results.hint The instance of the TestBox TestResult object to build a report on
  * @testbox.hint The TestBox core object
  * @options.hint A structure of options this reporter needs to build the report with
  */
  any function runReport(
    required testbox.system.TestResult results,
    required testbox.system.TestBox testbox,
    struct options={}
  ){
    getPageContext().getResponse().setContentType( "application/json" );
    return serializeJSON( arguments.results.getMemento() );
  }

}

Creating MockBox

The factory takes in one constructor argument that is not mandatory: generationPath. This path is a relative path of where the factory generates internal mocking stubs that are included later on at runtime. Therefore, the path must be a path that can be used using cfinclude. The default path the mock factory uses is the following, so you do not have to specify one, just make sure the path has WRITE permissions:

Hint If you are using Lucee or ACF10+ you can also decide to use the ram:// resource and place all generated stubs in memory.

mockBox = new testbox.system.MockBox();

// Within a TestBox Spec
getMockBox()
/testbox/system/stubs

Creating a Stub Object

In order to create a stub object you will use the : createStub() method.

public any createStub([boolean callLogging='true'], [extends], [implements])

Parameters:

  • callLogging - Add method call logging for all mocked methods

  • extends - Make the stub extend from certain CFC

  • implement - Make the stub adhere to an interface(s)

This method will create an empty stub object that you can use and mock with methods and properties. It can then be used in any code to satisfy dependencies meanwhile you build them. This is great to start working on projects where you are relying on other teams to build functionality but you have agreed on specific data or code interfaces. It is also super fancy as it can allow you to tell the stub to inherit from another CFC and look like it, or even pass in one or more interfaces that it must implement. If they are interfaces, then MockBox will generate all the necessary methods to satisfy those interfaces.

CreateStub() Inheritance

The createStub() method has an argument called extends that accepts a class path. This will create and generate a stub that physically extends that class path directly. This is an amazing way to create stubs that you can override with inherited functionality or just make it look like it is EXACTLY the type of object you want.

myService = mockbox.createStub(extends="model.security.MyService");

CreateStub() Interfaces

The createStub() method has an argument called implements that accepts a list of interface class paths you want the stub to implement. MockBox will then generate the stub and it will make sure that it implements all the methods the interfaces have defined as per their contract. This is such a fantastic and easy way to create a stub that looks and feels and actually has the methods an interface needs.

myFakeProvider = mockbox.createStub(implements="coldbox.system.cache.ICacheProvider");
myFakeProvider.getName();

Our Approach and Benefits

The approach that we take with MockBox is a dynamic and minimalistic approach. Why dynamic? Well, because we dynamically transform target objects into mock form at runtime. The API for the mocking factory is very easy to use and provides you a very simplistic approach to mocking.

We even use $()style method calls so you can easily distinguish when using or mocking methods, properties, etc. So what can MockBox do for me?

  • Create mock objects for you and keep their methods intact (Does not wipe methods, so you can do method spys, or mock helper methods)

  • Create mock objects and wipe out their method signatures

  • Create stub objects for objects that don't even exist yet. So you can build to interfaces and later build dependencies.

  • Decorate instantiated objects with mocking capabilities (So you can mock targeted methods and properties; spys)

  • Mock internal object properties, basically do property injections in any internal scope

  • State-Machine Results. Have a method recycle the results as it is called consecutively. So if you have a method returning two results and you call the method 4 times, the results will be recycled: 1,2,1,2

  • Method call counter, so you can keep track of how many times a method has been called

  • Method arguments call logging, so you can keep track of method calls and their arguments as they are called. This is a great way to find out what was the payload when calling a mocked method

  • Ability to mock results depending on the argument signatures sent to a mocked method with capabilities to even provide state-machine results

  • Ability to mock private/package methods

  • Ability to mock exceptions from methods or make a method throw a controlled exception

  • Ability to change the return type of methods or preserve their signature at runtime, extra cool when using stubs that still have no defined signature

  • Ability to call a debugger method ($debug()) on mocked objects to retrieve extra debugging information about its mocking capabilities and its mocked calls

Creating a Mock Object

In order to create a mock object you need to use any of the following methods: createMock(), createEmptyMock(), or prepareMock().

createMock()

Used to create a new mock object from scratch or from an already instantiated object.

public any createMock([string CLASSNAME], [any OBJECT], [boolean CLEARMETHODS='false'], [boolean CALLLOGGING='true'])

Parameters:

  • className - The class name of the object to create and mock

  • object - The instantiated object to add mocking capabilities to, similar to using prepareMock()

  • clearMethods - If true, all methods in the target mock object will be removed. You can then mock only the methods that you want to mock

  • callLogging - Add method call logging for all mocked methods only

collaborator = mockbox.createMock("model.myClass");

createEmptyMock()

Used to create a new mock object with all its method signatures wiped out, basically an interface with no real implementation. It will be up to you to mock all behavior.

public any createEmptyMock(string CLASSNAME, [any OBJECT], [boolean CALLLOGGING='true'])

Parameters:

  • className - The class name of the object to create and mock

  • object - The instantiated object to add mocking capabilities to, similar to using prepareMock()

  • callLogging - Add method call logging for all mocked methods only

user = mockbox.createEmptyMock("model.User");

prepareMock()

Decorate an already instantiated object with mocking capabilities. It does not wipe out the object's methods or signature, it only decorates it (mixes-in methods) with methods for mocking operations. This is great for doing targeted mocking for specific methods, private methods, properties and more.

public any prepareMock([any OBJECT], [boolean CALLLOGGING='true'])

Parameters:

  • object - The already instantiated object to prepare for mocking

  • callLogging - Add method call logging for all mocked methods only

myService = createObject("component","model.services.MyCoolService").init();
// prepare it for mocking
mockBox.prepareMock( myService );

Caution If call logging is turned on, then the mock object will keep track of all method calls to mocked methods ONLY. It will store them in a sequential array with all the arguments the method was called with (named or ordered). This is essential if you need to investigate if a method was called and with what arguments. You can also use this to inspect save or update calls based on mocked external repositories.

Sample:

Let's say that we have a user service layer object that relies on the following objects:

  • sessionstorage - a session facade object

  • transfer - the transfer ORM

  • userDAO - a data access object for complex query operations

We can start testing our user service and mocking its dependencies by preparing it in a test case CFC with the following setup() method:

component extends=”testbox.system.BaseSpec” {

    function beforeAll(){
        //Create the User Service to test, do not remove methods, just prepare for mocking.
        userService = createMock("model.UserService");

        // Mock the session facade, I am using the coldbox one, it can be any facade though
        mockSession= createEmptyMock(className='coldbox.system.plugins.SessionStorage');

        // Mock Transfer
        mockTransfer = createEmptyMock(className='transfer.com.Transfer');

        // Mock DAO
        mockDAO = createEmptyMock(className='model.UserDAO');

        //Init the User Service    with mock dependencies
        userService.init(mockTransfer,mockSession,mockDAO);
    }

    function run(){

        describe( "User Service", function(){
            it( "can get data", function(){
                // mock a query using mockbox's querysimulator
                mockQuery = querySim("id, name
                1|Luis Majano
                2|Alexia Majano");
                // mock the DAO call with this mocked query as its return
                mockDAO.$("getData", mockQuery);

                data = userService.getData();
                expect( data ).toBe( mockQuery );
            });
        });

    }

}

The service CFC we just injected mocked dependencies:

<cfcomponent name="UserService" output="False">

<cffunction name="init" returntype="UserService" output="False">
  <cfargument name="transfer">
  <cfargument name="sessionStorage">
  <cfargument name="userDAO">
  <cfscript>
    instance.transfer = arguments.transfer;
    instance.sessionStorage = arguments.sessionStorage;
    instance.userDAO = arguments.userDAO;

    return this;
  </cfscript>
</cffunction>

<cffunction name="getData" returntype="query" output="false">
    <cfreturn instance.userDao.getData()>
</cffunction>

</cfcomponent>

$getProperty() Method

This method can help you retrieve any public or private internal state variable so you can do assertions. You can also pass in a scope argument so you can not only retrieve properties from the variables scope but from any nested structure inside of any private scope:

Parameters:

  • name - The name of the property to retrieve

  • scope - The scope where the property lives in. The default is variables scope.

Mocking Methods

Once you have created a mock object, you can use it like the real object as it will respond exactly as it was coded. However, you can override its behavior by using the mocking methods that have been placed on the mocked object at run-time. The methods that you can call upon in your object are the following (we will review them in detail later):

$property() Method

This method is used in order to mock an internal property on the target object. Let's say that the object has a private property of userDAO that lives in the variables scope and the lifecycle for the object is controlled by its parent, in this case the user service. This means that this dependency is created by the user service and not injected by an external force or dependency injection framework. How do we mock this? Very easily by using the $property() method on the target object.

Parameters:

  • propertyName - The name of the property to mock

  • propertyScope - The scope where the property lives in. The default is variables scope.

  • mock - The object or data to inject and mock

Not only can you mock properties that are objects, but also mock properties that are simple/complex types. Let's say you have a property in your target object that controls debugging and by default the property is false, but you want to test the debugging capabilities of your class. So we have to mock it to true now, but the property exists in variables.instance.debugMode? No problem mate (Like my friend Mark Mandel says)!

$results() Method

This method can only be used in conjunction with $() as a chained call as it needs to know for what method are the results for.

The purpose of this method is to make a method return more than 1 result in a specific repeating sequence. This means that if you set the mock results to be 2 results and you call your method 4 times, the sequence will repeat itself 1 time. MUMBO JUMBO, show me!! Ok Ok, hold your horses.

As you can see, the sequence repeats itself once the call counter increases. Let's say that you have a test where the first call to a user object's isAuthorized() method is false but then it has to be true. Then you can do this:

$() Method

This is the method that you will call upon in order to mock a method's behavior and return results. This method has the capability of mocking a return value or even making the method throw a controlled exception. By default the mocked method results will be returned all the time the method is called. So if the mocked method is called twice, the results will always be returned.

Parameters:

  • method - The method you want to mock or spy on

  • returns - The results it must return, if not passed it returns void or you will have to do the mockResults() chain

  • preserveReturnType - If false, the mock will make the returntype of the method equal to ANY

  • throwException - If you want the method call to throw an exception

  • throwType - The type of the exception to throw

  • throwDetail - The detail of the exception to throw

  • throwMessage - The message of the exception to throw

  • callLogging - Will add the machinery to also log the incoming arguments to each subsequent calls to this method

  • preserveArguments - If true, argument signatures are kept, else they are ignored. If true, BEWARE with $args() matching as default values and missing arguments need to be passed too.

  • callback - A callback to execute that should return the desired results, this can be a UDF or closure. It also receives all caller arguments as well.

  • throwErrorCode - The error code to throw in the exception

The cool thing about this method is that it also returns the same instance of the object. Therefore, you can use it to chain calls to the object and do multiple mocking of methods all within the same line. Remember that if no returns argument is provided then the return is void

Examples

Let's do some samples now

any $getProperty(name [scope='variables']
expect( model.$getProperty("dataNumber", "variables") ).toBe( 4 );
expect( model.$getProperty("name", "variables.instance") ).toBe(  "Luis" );

Method Name

Return Type

Description

$()

The Mock

Used to mock a method on the mock object that can return, throw or be a void method.

$property()

The Mock

Mock a property in the object on any scope.

$getProperty(name, scope)

any

Retrieve any public or private internal state variable so you can do assertions and more mocking.

$results()

The Mock

Mock 1 or more results of a mock method call, must be chained to a $() or $().$args() call

$args()

The Mock

Mock 1 or more arguments in sequential or named order. Must be called concatenated to a $() call and must be followed by a concatenated $results() call so the results are matched to specific arguments.

querySim()

query

to denote columns. Ex: id, name 1 Luis Majano 2 Joe Louis

any $property(any obj, string propertyName, [string propertyScope='variables'], any mock)
//decorate our user service with mocking capabilities, just to show a different approach
userService = getMockBox().prepareMock( createObject("component","model.UserService") );

//create a mock dao and mock the getUsers() method
mockDAO=getMockBox().createEmptyMock("model.UserDAO").$("getUsers",QueryNew(""));

//Inject it as a property of the user service, since no external injections are found. variables scope is the default.
userService.$property(propertyName="userDAO",mock=mockDAO);

//Test a user service method that uses the DAO
results = userService.getUsers();
assertTrue( isQuery(results) );
//decorate the cache object with mocking capabilties
cache = getMockBox().createMock(object=createObject("component","MyCache"));

//mock the debug property
cache.$property(propertyName="debugMode",propertyScope="instance",mock=true);
$(...).$results(...)
//Mock 3 values for the getSetting method
controller.$("getSetting").$results(true,"cacheEnabled","myapp.model");

//Call getSetting 1
<cfdump var="#controller.getSetting()#">
Results = true

//Call getSetting 2
<cfdump var="#controller.getSetting()#">
Results = "cacheEnabled"

//Call getSetting 3
<cfdump var="#controller.getSetting()#">
Results = "myapp.model"

//Call getSetting 4
<cfdump var="#controller.getSetting()#">
Results = true

//Call getSetting 5
<cfdump var="#controller.getSetting()#">
Results = "cacheEnabled"
mockUser = getMockBox().createMock("model.User");
mockUser.$("isAuthorized").$results(false,true);
any $(string method, [any returns], boolean preserveReturnType='true', [boolean throwException='false'], [string throwType=''], [string throwDetail=''], [string throwMessage=''], [boolean callLogging='false'], [boolean preserveArguments='false'], [any callback])
function beforeAll(){
    mockUser = createMock("model.security.User");

    //Mock several methods all in one shot!
    mockUser.$("isFound",false).$("isDirty",false).$("isSaved",true);
}
//make exists return true in a mocked session object
mockSession.$(method="exists",returns=true);
expect(mockSession.exists('whatevermanKey')).toBeTrue();

//make exists return true and then false and then repeat the sequence
mockSession.$(method="exists").$results(true,false);
expect( mockSession.exists('yeaaaaa') ).toBeTrue();
expect( mockSession.exists('nada') ).toBeFalse();

//make the getVar return a mock User object
mockUser = createMock(className="model.User");
mockSession.$(method="getVar",results=mockUser);

expect( mockSession.getVar('sure') ).toBe( mockUser );

//Make the call to user.checkPermission() throw an invalid exception
mockUser.$(method="checkPermission",
        throwException=true,
        throwType="InvalidPermissionException",
        throwMessage="Invalid permission detected",
        throwDetail="The permission you sent was invalid, please try again.");

try{
    mockUser.checkPermission('invalid');
}
catch(Any e){
    if( e.type neq "InvalidPermissionException"){
      fail('The type was invalid #e.type#');
    }
}

//mock a method with call logging
mockSession.$(method="setVar",callLogging=true);
mockSession.setVar("Hello","Luis");
mockSession.setVar("Name","luis majano");
//dump the call logs
<cfdump var="#mockSession.$callLog()#">

$args() Method

This method is used to tell MockBox that you want to mock a method with a SPECIFIC number of argument calls. Then you will have to set the return results for it, but this is absolutely necessary if you need to test an object that makes several method calls to the same method with different arguments, and you need to mock different results coming back. Example, let's say you are using a ColdBox configuration bean that holds configuration data. You make several calls to the getKey() method with different arguments:

configBean.getKey('DebugMode');
configBean.getKey('OutgoingMail');

How in the world can I mock this? Well, using the mock arguments method.

//get a mock config bean
mockConfig = getMockBox().createEmptyMock("coldbox.system.beans.ConfigBean");
//mock the method with args
mockConfig.$("getKey").$args("debugmode").$results(true);
mockConfig.$("getKey").$args("OutgoingMail").$results('devmail@mail.com');

//Then you can call and get the expected results

Hint So remember that if you use the $args() call, you need to tell it what kind of results you are expecting by calling the $results() method after it or you might end up with an exception.

TestBox BDD Refcard
TestBox BDD Refcard
TestBox xUnit Refcard
TestBox xUnit Refcard