TestBox allows you to create BDD expectations with our expectations and matcher API DSL. You start by calling our expect()
method, usually with an actual value you would like to test. You then concatenate the expectation of that actual value/function to a result or what we call a matcher. You can also concatenate matchers (as of v2.1.0) so you can provide multiple matching expectations to a single value.
expect( 43 ).toBe( 42 );
expect( () => calculator.add(2,2) ).toThrow();
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 API Docs.
toBeTrue( [message] ) : value to true
toBeFalse( [message] ) : value to be false
toBe( expected, [message] ) : Assert something is equal to each other, no case is required
toBeWithCase( expected, [message] ) : Expects with case
toBeNull( [message] ) : Expects the value to be null
toBeInstanceOf( class, [message] ) : To be the class instance passed
toMatch( regex, [message] ) : Matches a string with no case-sensitivity
toMatchWithCase( regex, [message] ) : Matches with case-sensitivity
toBeTypeOf( type, [message] ) : Assert the type of the incoming actual data, it uses the internal ColdFusion isValid() function behind the scenes, type can be array, binary, boolean, component, date, time, float, numeric, integer, query, string, struct, url, uuid plus all the ones from isValid()
toBe{type}( [message] ) : Same as above but more readable method name. Example: .toBeStruct(), .toBeArray()
toBeEmpty( [message] ) : Tests if an array or struct or string or query is empty
toHaveKey( key, [message] ) : Tests the existence of one key in a structure or hash map
toHaveDeepKey( key, [message] ) : Assert that a given key exists in the passed in struct by searching the entire nested structure
toHaveLength( length, [message] ) : Assert the size of a given string, array, structure or query
toThrow( [type], [regex], [message] );
toBeCloseTo( expected, delta, [datepart], [message] ) : Can be used to approximate numbers or dates according to the expected and delta arguments. For date ranges use the datepart values.
toBeBetween( min, max, [message] ) : Assert that the passed in actual number or date is between the passed in min and max values
toInclude( needle, [message] ) : Assert that the given "needle" argument exists in the incoming string or array with no case-sensitivity, needle in a haystack anyone?
toIncludeWithCase( needle, [message] ) : Assert that the given "needle" argument exists in the incoming string or array with case-sensitivity, needle in a haystack anyone?
toBeGT( target, [message] ) : Assert that the actual value is greater than the target value
toBeGTE( target, [message] ) : Assert that the actual value is greater than or equal the target value
toBeLT( target, [message] ) : Assert that the actual value is less than the target value
toBeLTE( target, [message] ) : Assert that the actual value is less than or equal the target value
You can prefix your expectation with the not
operator to easily cause negative expectations for any matcher. When you read the API Docs or the source, you will find nowhere the not methods. This is because we do this dynamically by convention.
expect( actual )
.notToBe( 4 )
.notToBeTrue();
.notToBeFalse();
Our default syntax for expecting exceptions is to use our closure approach concatenated with our toThrow()
method in our expectations or our throws()
method in our assertions object.
Info Please always remember to pass in a closure to these methods and not the actual test call:
function(){ myObj.method();}
Example
expect( function(){ myObj.method(); } ).toThrow( [type], [regex], [message] );
$assert.throws( function(){ myObj.method; }, [type], [regex], [message] )
This will execute the closure in a nested try/catch
block and make sure that it either threw an exception, threw with a type, threw with a type and a regex match of the exception message. If you are in an environment that does not support closures then you will need to create a spec testing function that either uses the expectedException
annotation or function call:
function testMyObj(){
expectedException( [type], [regex], [message] );
}
function testMyObj() expectedException="[type]:[regex]"{
// this function should produce an exception
}
Caution Please note that the usage of the
expectedException()
method can ONLY be used while in synchronous mode. If you are running your tests in asynchronous mode, this will not work. We would recommend the closure or annotation approach instead.
TestBox comes with a decent amount of matchers that cover what we believe are common scenarios. However, we recommend that you create custom matchers that meet your needs and criteria so that you can avoid duplication and have re-usability.
Every custom matcher is a function and must have the following signature, with MyMatcher
being the name of your custom matcher function:
boolean function MyMatcher( required expectation, args={} )
The matcher function receives the expectation
object and a second argument which is a structure of all the arguments with which the matcher function was called with. It must then return a true or a false depending if it passes your criteria. It will most likely use the expectation
object to retrieve the actual and isNot values. It can also set a custom failure message on the expectation object itself by using the message
property of the expectation
object.
boolean function reallyFalse( expectation, args={} ){
expectation.message = ( structKeyExists( args, "message" ) ? args.message : "[#expectation.actual#] is not really false" );
if( expectation.isNot )
return ( expectation.actual eq true );
else
return ( expectation.actual eq false );
}
}
The next step is to tell TestBox about your matcher.
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.
function beforeAll(){
addMatchers( {
toBeAwesome : function( expectation, args={} ){ return expectation.actual gte 100; },
toBeGreat : function( expectation, args={} ){ return expectation.actual gte 1000; },
// please note I use positional values here, you can also use name-value arguements.
toBeGreaterThan : function( expectation, args={} ){ return ( expectation.actual gt args[ 1 ] ); }
} );
}
After it is registered, then you can use it.
it("A custom matcher", function(){
expect( 100 ).toBeAwesome();
expect( 5000 ).toBeGreat();
expect( 10 ).toBeGreaterThan( 5 );
});
You can also store a plethora of matchers (Yes, I said plethora), in a class and register that as the matchers via its instantiation path. This provides much more flexibility and re-usability for your projects.
addMatchers( "model.util.MyMatchers" );
You can also register an instance:
addMatchers( new models.util.MyMatchers() );