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...
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...
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...
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.
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 and expectations library but also ships with MockBox, A mocking and stubbing companion. It also supports xUnit style of testing and MXUnit compatibilities.
Here are a simple listing of features TestBox brings to the table:
BDD style testing
xUnit style testing
Code Coverage via FusionReactor
Testing life-cycle methods
MockBox integration for mocking and stubbing
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
TAP (Test Anything Protocol)
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!
TestBox is maintained under the Semantic Versioning guidelines as much as possible.Releases will be numbered with the following format:
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
TestBox and MockBox are open source and licensed under the Apache 2 License. If you use them please try to make mention of it in your code or web site.
Copyright by Ortus Solutions, Corp
TestBox is a registered trademark by Ortus Solutions, Corp
The ColdBox Websites, Documentation, logo and content have a separate license and they are a separate entity.
BoxTeam Slack : https://boxteam.herokuapp.com
CFML Slack: Look for the box-products
channel: http://cfml.slack.com
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: https://github.com/Ortus-Solutions/TestBox
TestBox is a professional open source software backed by Ortus Solutions, Corp offering services like:
Custom Development
Professional Support & Mentoring
Training
Server Tuning
Security Hardening
Code Reviews
Official Site: https://www.ortussolutions.com/products/testbox
Current API Docs: https://apidocs.ortussolutions.com/testbox/current
Source Code: https://github.com/Ortus-Solutions/TestBox
Bug Tracker : https://ortussolutions.atlassian.net/browse/TESTBOX
Twitter: @ortussolutions
Facebook: https://www.facebook.com/ortussolutions
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
[] - Code coverage doesn't always capture ending parenthesis
[] - When the key existed notToHaveKey
wasn't failing
[] - missing raw_trace
in simple.cfm
[] - Lowercase cbstreams
for case-sensitive file systems
[] - Moved invoke
block from bottom of method into if block for "detect negation" instead of stack overflow errors
[] - Add formatting config and scripts
This was asked for a long time ago and finally we can oblige. You can now prefix any of the suite and spec methods with the letter f
and they will be focused. Meaning, ONLY those suites or specs will be executed.
fit(), fthen()
- Spec Methods
fdescribe(), fscenario(), fstory(), ffeature(), fgiven(), fwhen()
- Suite Methods
This is great if you ONLY want certain things to execute instead of the x
prefix which was used to EXCLUDE specs and suites.
Here are the results:
toHaveKey()
can now work with key listsHow many times did you want to check if a struct had ALL the keys or NO keys at all. Well, now you can. You can pass a list of keys into the toHaveKey()
expectation and we will make sure your structure has ALL the keys in it.
fail()
ImprovementsYou can now pass in a detail to the fail messages that can be used to track exception details, stacktraces, extended info and much more. Sometimes, this can be a life-saver when dealing with toThrow()
expectations:
Which is exactly what we did. The catching of expected exceptions now pass in the exact stacktrace where they failed instead of the nested exception. Which goes nicely into our next major improvement.
We all hate them and not easy to track. In this release we at least try to add more debugging on the results by introducing the following new keys and output on the reporters:
failDetail
failExtendedInfo
failStacktrace
The reporters know will output the CFML engine the report is executed on. Also, the raw json/xml reporters will also report back the CFML engine and version.
[TESTBOX-251] - Scripts don't output if runner.cfm
has enableCFoutputOnly
set to true
[TESTBOX-252] - JSON,xml,Junit, Ant reporters fails for integration tests with cfhtmlhead and cfheader
[TESTBOX-257] - Update build process to upload the right version assets
[TESTBOX-255] - Added new spec stats to track nested exceptions: failDetail, failExtendedInfo, failStacktrace
[TESTBOX-256] - Add a detail argument to the fail()
method to allow for more in-depth tracking of failures, especially when using the `toThrow()`
expectation
[TESTBOX-258] - Added CFMLEngine
and CFMLEngineVersion
to results memento so any consumer can display this information
[TESTBOX-253] - Display the CFML engine in use when using the simple reporter
[TESTBOX-198] - Add support to fit, fdescribe, fscenario,fwhen, fgiven, fstory, ffeature, fthen
[TESTBOX-254] - check if fail origin is an array in case internal TestBox borks out in all the reporters
[TESTBOX-259] - Turn off code coverage when clicking a link in the HTML reporter
[TESTBOX-260] - toHaveKey()
now accepts a list of keys that MUST or MUST NOT exist in the collection
TestBox can be downloaded from https://www.ortussolutions.com/products/testbox or can be installed via CommandBox CLI. CommandBox is our preferred approach for all package installations, updates and more.
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.
You can also clone from Github and help us out :)
Once you have testbox installed, you'll need a quick way to set up a testing harness. The generate harness
command will add a new /tests
folder to your application with a few example tests to get you started.
Lucee 5.x+
ColdFusion 11+
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.
test-visualizer: A static visualizer of json reports. Just drop in a test-results.json
and run it!
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.
TestBox has the following supported IDE Tools:
In this section you will find the release notes for each version we release under this major version. If you are looking for the release notes of previous major versions use the version switcher at the top left of this documentation book. Here is a breakdown of our major version releases.
In this release we focused on dropping engine supports for legacy CFML engines. We had a major breakthrough in introducing Code Coverage thanks to the FusionReactor folks as well. This major release came also with a new UI for all reporters and streamlining the result view ports.
This version spawned off with over 8 minor releases. We focused on taking TestBox 1 to yet a high level. Much more attention to detail and introducing modern paradigms like given-when-then. Multiple interception points, async executions and ability to chain methods.
This was our first major version for TestBox. We had completely migrated from MXUnit and it introduced BDD to the ColdFusion (CFML) world.
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.
In this section we will go over the major features of our behavior driven development syntax. Let's begin.
The source code for this book is hosted in GitHub: https://github.com/Ortus-Solutions/testbox-docs. You can freely contribute to it and submit pull requests. The contents of this book is copyright by Ortus Solutions, Corp 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 examples in this book are done in cfscript
.
The majority of code generation and running of examples are done via CommandBox: The ColdFusion (CFML) CLI, Package Manager, REPL - http://www.ortussolutions.com/products/commandbox
All ColdFusion examples designed to run on the open soure Railo Platform or Adobe ColdFusion 9.0.2+
Flash, Flex, ColdFusion, and Adobe are registered trademarks and copyrights of Adobe Systems, Inc.
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.
We highly encourage contribution to this book and our open source software. The source code for this book can be found in our GitHub repository where you can submit pull requests.
15% of the proceeds of this book will go to charity to support orphaned kids in El Salvador - http://www.harvesting.org/. 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 (http://www.harvesting.org/) 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.
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.
Luis Majano is a Computer Engineer with over 16 years of software development and systems architecture experience. He was born in San Salvador, El Salvador 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 Florida International University. Luis resides in The Woodlands, Texas with his beautiful wife Veronica, baby girl Alexia and baby boy Lucas!
He is the CEO of Ortus Solutions, 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 Inland Empire. You can read his blog at www.luismajano.com
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
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 ITESM, Mexico, he went back to his home country where he worked as the COO of Industrias Bendek S.A.. 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.
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
TestBox 3.0.0 is a major release. It has compatibility changes that you should be aware and lots of good feaures!
The major compatibility issues are the engine support removals:
Lucee 4.5 Support Dropped
Adobe ColdFusion 10 Dropped
It is easy to update, just type update testbox
and you are done!
The most notable features of this release can be found below.
This has been fully documented and you can find much more information in the code coverage section.
The static test visualizer is basically the simple reporter but works in offline mode. This means that it will read a static TestBox results json file and create the report for it in the browser. This is incredibly useful for CI integrations and representing any TestBox results json file visually.
You will find the analyzer under /test-visualizer
in the root of the TestBox installation.
To run it all you need to do is put alongside of it a test-results.json
file and then run the index.html
and voila! Test Results Visualized!
[TESTBOX-234] - bddrunner.cfm: now compiles properly on ACF
[TESTBOX-248] - Skip methods for given/when/then fail without `this` reference
[TESTBOX-236] - Add CodeCoverage Reporter to TestBox
[TESTBOX-239] - Update the UI for the code coverage reporting and code visualizer
[TESTBOX-243] - Complete UI updates for test reporters
[TESTBOX-245] - Static Test Visualizer
[TESTBOX-237] - Update usage of htmleditformat to encodeForHTML
[TESTBOX-242] - Removal of old cfml engines support acf10 and lucee 4.5
[TESTBOX-244] - streamify the code coverage collection
[TESTBOX-249] - Add original method name to mocking function so it can help in debugging
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.
Our RefCards will get you up and running in no time.
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. Don't worry, we will cover what's a suite and a spec as well.
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 tear down 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.
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 describe
s. 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
).
The following bundle creates suites dynamically, by looping over test metadata.
A test suite in TestBox is a collection of specifications that will model what you want to test. The way the suite is express can be of many different types as we will investigate.
Test suite is a container that has a set of tests which helps testers in executing and reporting the test execution status.
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.
The describe()
function is also aliased with the following names:story(), feature(), scenario(), given(), when()
There are more arguments which you can see below:
A spec is a declaration that will usually test your system with a requirement. They 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 .
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.
The it()
function is also aliased as then()
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:
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.
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.
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 .
Argument | Required | Default | Type | Description |
| true | --- | string | The title of the suite to register |
| true | --- | closure/udf | The closure that represents the test suite |
| false | --- | string/array | The list or array of labels this suite group belongs to |
| false | false | Boolean | If you want to parallelize the execution of the defined specs in this suite group. |
| 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. |
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 |
Specs and suites can be focused so ONLY those suites and specs execute. You will do this by prefixing certain functions with the letter f
or by using the focused
argument in each of them. The reporters will show that these suites or specs where execute ONLY The functions you can prefix are:
it()
describe()
story()
given()
when()
then()
feature()
Please note that if a suite is focused, then all of its children will execute.
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.
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 BDD style.
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.
Given-When-Then 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 Specification By Example)
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 behavioural specifications 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 Gherkin language; using it we can gather and document the requirements as:
TestBox provides you with feature()
, scenario()
and story()
wrappers for describe()
blocks. As such we can write our requirements in test form like so:
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.
If you prefer to gather requirements as User Stories then you may prefer to take advantage of the story()
wrapper for describe()
instead.
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.
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.
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:
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.
TestBox since v2.3.0 also allows you to declare life-cycle methods via annotations. Please see our Annotations section for more information.
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.
beforeEach
aroundEach (first half)
it() (the spec.body()
call)
aroundEach (second half)
afterEach()
Here's an example:
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.
Outermost beforeEach()
call
Innermost beforeEach()
call
Outermost aroundEach()
call (first half)
Innermost aroundEach()
call (first half)
The it()
block
Innermost aroundEach()
calls (second half)
Outermost aroundEach()
call (second half)
Innermost afterEach()
call
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 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.
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()
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.
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:
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
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
NodeJS : User-contributed:
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.
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:
Info We encourage you to read the API docs included in the distribution for the complete parameters for each method.
run()
ArgumentsHere are the arguments you can use for initializing TestBox or executing the run()
method
runRemote()
argumentsHere are the arguments you can use for executing the runRemote()
method:
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.
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:
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.
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.
You can run tests via SOAP by leveraging the runRemote() method. The WSDL URL will be
HTTP/REST Runner You can run tests via HTTP/REST by leveraging the runRemote() endpoint. The URL will be
There is a user-contributed NodeJS Runner that looks fantastic and can be downloaded here:
Just use node to install:
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 ```
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. |
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: |
eagerFailure | false | false | boolean | If true, then after testing a bundle if there are any failures or errors no more testing will be performed. |
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. |
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 xUnit functionalities of TestBox will require Railo 4.1+, Lucee 4.5+ or ColdFusion 9.01+.
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.
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.
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 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.
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.
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 API 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.
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 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
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.
Each test function can also have some cool annotations attached to it.
TestBox comes with two flavors of testing: and .
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 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.
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.
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.
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
NodeJS : User-contributed:
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:
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
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:
Info We encourage you to read the API docs included in the distribution for the complete parameters for each method.
run()
ArgumentsHere are the arguments you can use for initializing TestBox or executing the run()
method
runRemote()
argumentsHere are the arguments you can use for executing the runRemote()
method:
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.
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:
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.
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.
You can run tests via SOAP by leveraging the runRemote() method. The WSDL URL will be
HTTP/REST Runner You can run tests via HTTP/REST by leveraging the runRemote() endpoint. The URL will be
There is a user-contributed NodeJS Runner that looks fantastic and can be downloaded here:
Just use node to install:
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 ```
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.
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. |
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 |
Argument | Required | Default | Type | Description |
bundles | true | --- | 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. |
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 |
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.
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
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
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.
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
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 API Docs for more information.
Test suites are a collection of testing methods or specifications (specs) inside of a testing bundle CFC. Let's investigate the styles.
TestBox supports the concept of assertions 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 API 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:
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.
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.
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.
Here is a typical example:
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)
PostsTest.cfc
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.
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.
Caution If you activate the
asyncAll
flag for asynchronous testing, you HAVE to make sure your tests are also thread safe and appropriately locked.
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
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).
Each test function can also have some cool annotations attached to it.
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:
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.
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.
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.
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.
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.
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 .
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
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:
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.
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:
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.
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.
You can also register more than 1 CFC by using a list or an array:
Here is the custom assertions CFC source:
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:
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.
The next step is to tell TestBox about your matcher.
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.
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.
After it is registered, then you can use it.
You can also register a CFC instance:
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.
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.
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.
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.
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.
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
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.
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:
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:
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.
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:
We encourage you to read the API docs included in the distribution for the complete parameters for each method.
run()
ArgumentsHere are the arguments you can use for initializing TestBox or executing the run()
method
runRemote()
ArgumentsHere are the arguments you can use for executing the runRemote()
method of the TestBox object:
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.
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.
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.
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.
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.
You can run tests via SOAP by leveraging the runRemote()
method. The WSDL URL will be
You can run tests via HTTP/REST by leveraging the runRemote()
endpoint. The URL will be
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.
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
You can use node to install as well:
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
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.
TestBox is fully compliant with MXUnit 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.
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.
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.
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.
You can also build your own reporters by implementing our core class: testbox.system.reporters.IReport
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:
Here is a sample reporter for you that generates JSON for the output.
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.
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 from our downloads page or clone via github. Also read our installation section.
MockBox can be downloaded from http://www.ortussolutions.com/products/testbox or can be installed via CommandBox as part of the TestBox package.
"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 The Evolutionary Nature of Professional Software Development)
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.
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 Scott Bain describes in his Emergent Design book:
"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 :)
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.
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
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.
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.
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.
In order to create a mock object you need to use any of the following methods: createMock()
, createEmptyMock()
, or prepareMock()
.
Used to create a new mock object from scratch or from an already instantiated object.
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
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.
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
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.
Parameters:
object - The already instantiated object to prepare for mocking
callLogging - Add method call logging for all mocked methods only
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:
The service CFC we just injected mocked dependencies:
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
Let's do some samples now
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.
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:
How in the world can I mock this? Well, using the mock arguments method.
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.
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):
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)!
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 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
This method is used to tell MockBox that you want to mock a method with to throw a specific exception. The exception will be thrown instead of the method returning results. This is an alternative to passing the exception in the initial $()
call. In addition to the fluent API, the $throws()
method also has the benefit of being able to be tied to specific $args()
in a mocked object.
To continue with our getKey()
example:
We want to test that keys that don't exists throw a MissingSetting
exception. Let's do that using the $throws()
method:
Hint Remember that the
$throws()
call must be chained to a$()
or a$args()
call.
Get the number of times a method has been called or the entire number of calls made to ANY mocked method on this mock object. If the method has never been called, you will receive a 0. If the method does not exist or has not been mocked, then it will return a -1.
Parameters:
methodName - Name of the method to get the counter for (Optional)
This method is NOT injected into mock objects but avaialble via MockBox directly in order to create queries very quickly. This is a great way to simulate cfquery calls, cfdirectory or any other cf tag that returns a query.
This method is a quick notation for the $times(0)
call but more expressive when written in code:
Parameters:
* methodName - The optional method name to assert the number of method calls
Examples:
This method is used to assert how many times a mocked method has been called or ANY mocked method has been called.
Parameters:
count - The number of times any method or a specific mocked method has been called
methodName - The optional method name to assert the number of method calls
Examples
The following methods are also mixed in at run-time into mock objects and they will be used to verify behavior and calls from these mock/stub objects. These are great in order to find out how many mocked methods calls have been made and to find out the arguments that where passed to each mocked method call.
This method can help you verify that at least a minimum number of calls have been made to all mocked methods or a specific mocked method.
Parameters:
minNumberOfInvocations - The min number of calls to assert
methodName - The optional method name to assert the number of method calls
This method can help you verify that only ONE mocked method call has been made on the entire mock or a specific mocked method. Useful alias!
Parameters:
methodName - The optional method name to assert the number of method calls
Examples:
Method Name
Return Type
Description
$count([methodName])
Numeric
Get the number of times all mocked methods have been called on a mock or pass in a method name and get a the method's call count.
$times(count,[methodName]) or $verifyCallCount(count,[methodName])
Numeric
Assert how many calls have been made to the mock or a specific mock method
$never([methodName])
Boolean
Assert that no interactions have been made to the mock or a specific mock method: Alias to $times(0)
$atLeast(minNumberOfInvocations,[methodName])
Boolean
Assert that at least a certain number of calls have been made on the mock or a specific mock method
$once([methodName])
Boolean
Assert that only 1 call has been made on the mock or a specific mock method
$atMost(maxNumberOfInvocations, [methodName])
Boolean
Assert that at most a certain number of calls have been made on the mock or a specific mock method.
$callLog()
struct
Retrieve the method call logger structure of all mocked method calls.
$reset()
void
Reset all mock counters and logs on the targeted mock.
$debug()
struct
Retrieve a structure of mocking debugging information about a mock object.