Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Describe(), Feature(), Scenario(), Given(), When()
A test suite in TestBox is a collection of specifications that model what you want to test. As we will investigate, the way the suite is expressed can be of many different types.
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:
Argument
Required
Default
Type
Description
title
true
---
string
The title of the suite to register
body
true
---
closure/udf
The closure that represents the test suite
labels
false
---
string/array
The list or array of labels this suite group belongs to
asyncAll
false
false
Boolean
If you want to parallelize the execution of the defined specs in this suite group.
skip
false
false
Boolean
A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean.
BDD stands for Behavioral Driven Development. It is a software development process that aims to improve collaboration between developers, testers, and business stakeholders. BDD involves creating automated tests that are based on the expected behavior of the software, rather than just testing individual code components. This approach helps ensure that the software meets the desired functionality and is easier to maintain and update in the future.
In traditional xUnit, you focused on every component's method individually. In BDD, we will focus on a feature
or story
to complete, which could include testing many different components to satisfy the criteria. TestBox allows us to create these types of texts with human-readable functions matching our features/stories and expectations.
Expectations are self-concatenated strings that evaluate an actual value to an expected value or condition. These are initiated by the global TestBox method called expect()
which takes in a value called the actual value or expectAll()
which takes in an array or struct which will be the actual value. It is concatenated in our expectations DSL with a matcher function that will most likely take in an expected value or condition to test. You can also concatenate the matchers and do multiple evaluations on a single actual value.
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.
TestBox has a plethora (That's Right! I said Plethora) of matchers that are included in TestBox. The best way to see all the latest matchers is to visit our API 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 custom matchers.
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.
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 Bundle is a CFC
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. Usually they will have a name that ends with *Spec
or *Test.
This bundle CFC can contain 2 life-cycle functions and a single run()
function where you will write 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 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 reports can pick up later. 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.
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 SUT (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 story.
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()
- except it()
has title
when then()
uses then
instead
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 closures, so please remember them. We recommend always using the variables
scope for easy access and distinction.
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:
When using the then()
function instead of it()
the title argument name is then
instead of title
for the it()
function
is a style of writing tests where you describe the state of the code you want to test (Given
), the behavior you want to test (When
) and the expected outcome (Then
). (See )
Testbox supports the use of function names given()
and when()
in-place of describe()
function calls. The then()
function call is an alternative for it()
function calls. The advantage of this style of is that you can gather your requirements and write your tests in a common language that can be understood by developers and stake-holders alike. This common language format is often referred to as the language; using it we can gather and document the requirements as:
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.
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.
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. You can alternatively choose to skip specific labels when a runner executes with excludes
attached.
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.
You can pass in an argument called data
, which is a struct
of dynamic data, to all life-cycle methods. This is useful when creating dynamic suites and specifications. This data
will then be passed into the executing body for each life-cycle method for you.
Here is a typical example:
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 or by using the skip( message, detail )
function. The reporters will show that these suites or specs were skipped from execution. The functions you can prefix are:
it()
describe()
story()
given()
when()
then()
feature()
Here are some examples:
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.
You can now use the skip( message, dteail )
method to skip any spec or suite a-la-carte instead of as an argument to the function definitions. This lets you programmatically skip certain specs and suites and pass a nice message.
Global callbacks affect the execution of the entire test bundle CFC and all of its suites and specs.
Executes once before all specs for the entire test bundle CFC. A great place to initialize the environment the bundle needs for testing.
Executes once after all specs for the entire test bundle CFC. A great place to teardown the environment the bundle needed for testing.
Copy
Executes once so it can capture all your describe
and it
blocks so they can be executed by a TestBox runner.
The following callbacks influence the execution of specification methods: it(), then()
. The great flexibility of the BDD approach is that it allows you to nest describe
, feature
, story
, given
, scenario
, when
suite blocks to create very human readable and organized documentation for your tests. Each suite 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.
TestBox will walk down the tree (from the outermost suite) for beforeEach()
operations and out of the tree (from the innermost suite) for afterEach()
operations.
The body
closure will receive have the following signature:
The body
closure will receive have the following signature:
Here are some examples:
The body
closure will receive have the following signature:
The spec
is the currently executing specification, the suite
is the suite this life-cycle is embedded in and data
is the data binding, if any.
Here is an example:
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.
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
Argument | Required | Default | Type | Description |
---|---|---|---|---|
If you prefer to gather requirements as then you may prefer to take advantage of the story()
wrapper for describe()
instead.
You can find the API docs for testbox
and the testResults
arguments here:
Executes before every single spec in a single suite block and receives the currently executing spec and any with. The body
is a closure/lambda that will fire and the data
argument is a way to with a struct of data that can flow down to specs.
Executes after every single spec in a single suite block and receives the currently executing spec and any with. The body
is a closure/lambda that will fire and the data
argument is a way to with a struct of data that can flow down to specs.
Executes around the executing spec so you can provide code that will surround the execution of the spec. It's like combining before
and after
in a single operation. The body
is a closure/lambda that will fire and the data
argument is a way to with a struct of data that can flow down to specs. This is the only way you can use CFML constructs that wrap around code like: try/catch, transaction, for, while, etc.
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
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: https://www.npmjs.com/package/testbox-runner
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.
Running tests is essential of course. There are many ways to run your tests, we will see the basics here, and you can check out our Running Tests section in our in-depth guide.
The easiest way to run your tests is to use the TestBox CLI via the testbox run
command. Ensure you are in the web root of your project or have configured the box.json
to include the TestBox runner in it as shown below. If not CommandBox will try to run by convention your site + test/runner.cfm
for you.
You can also pass the runner URL via the testbox run
command. Try out the testbox run help
command.
Here is a simple box.json
config that has a runner and some watcher config.
Check out the watcher command: testbox watch
Every test harness also has an HTML runner you can execute. By convention the URL is
This will execute ALL tests in the tests/specs
directory for you.
You can also target a specific spec to execute via the URL
TestBox ships with a global runner that can 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.