spectacular
Version:
Advanced BDD framework for CoffeeScript and JavaScript
342 lines (255 loc) • 12.2 kB
Markdown
---
title: Writing Tests
date: 2013-07-17 20:26
author: Cédric Néhémie <cedric.nehemie@gmail.com>
template: page.jade
----
The tests in Spectacular are created through two classes, namely `Example` and `ExampleGroup`. The `Example` class represent the actual executed test while `ExampleGroup` represent a suite of tests and represent the organisation of your tests.
## Examples And ExampleGroups
`ExampleGroups` are created with either the `describe` or `context` methods. New aliases can be created with `spectacular.env.createExampleGroupAlias(newName)`. The `ExampleGroup` class has some special properties regarding subjects that are detailed in the [Auto subjects](#Auto-subjects) section.
`Examples` are created with `it`, `the` or `specify`, and new aliases can be created with `spectacular.env.createExampleAlias(newName)`.
```coffeescript
describe 'a group', ->
context 'a child group', ->
specify 'an example', ->
[0,1,2].should contains 1
```
## Examples States
An example can end with one of the following five state:
* `success`: When all the expectations were successful.
* `failure`: When at least one expectation failed.
* `errored`: When an error was raised during the test execution.
* `skipped`: When dependencies of a test were not met.
* `pending`: When no assertions was made during the test.
## Pending Examples
Examples that doesn't have a block, or that have a block that doesn't contains any assertions are considered as `pending`.
You can force an example to be marked as pending by either calling the pending method in its block or using the `xit` method.
```coffeescript
it 'is pending'
it 'is pending', ->
it 'is pending', -> pending()
xit 'is pending', -> [1,2,3].should contains 1
```
Example groups without block or without examples are also considered as pending. It's also possible to use either `xdescribe` or `xcontext` to mark a group as pending.
```coffeescript
describe 'a pending group'
describe 'a pending group', ->
xdescribe 'a pending group', ->
# ...
xcontext 'a pending context', ->
# ...
```
## Asynchronous Examples
To create an asynchronous example, just specify an argument to the example
block. A pending promise will be passed to the block. The example can then
either `resolve` or `reject` the promise.
```coffeescript
specify 'an asynchronous example', (async) ->
doSomethingAsync (err, res) ->
return async.reject err if err?
res.should exist
async.resolve()
```
In the case the promise is rejected the passed-in reason will be used as failure message.
By default asynchronous example have a timeout limit of 5000ms, but it can be changed using the `rejectAfter` method of the promise.
```coffeescript
specify 'a heavy asynchronous example', (async) ->
async.rejectAfter 60000, 'timeout message'
# ...
```
## Conditioned Examples
Examples can depends on other examples. If all their dependency succeed the example is run, otherwise the example is marked as `skipped`.
There's two type of dependencies, an example can either depends on examples from another context:
```coffeescript
describe 'first context', id: 'contextId', ->
# ...
describe 'depending context', ->
dependsOn 'contextId'
# ...
```
In that case, the `depending context`'s example will only be run when all the examples in `first context` succeed.
The other type of dependencies is called cascading dependency, examples in a sub-context will only if the examples in its parent context have all succeed.
```coffeescript
describe 'parent context', ->
specify 'a parent example', ->
# ...
whenPass ->
specify 'a child example', ->
# ...
```
In that case, the child example will only run if the parent example succeed.
## Examples Subject
As in RSpecs, example groups can define a subject that will be available in all their examples:
```coffeescript
describe 'a subject', ->
subject -> {}
it -> should exist
```
Some methods such `its`, `itsReturn` and `itsInstance` will test some aspect of a previous subject:
* `its 'property'` will use the content of the specified property as subject for the test block.
* `itsReturn` when the current subject is a function will use the value returned by the function as the subject for the test block. It accept two options `with` and `inContext` to set respectively the arguments of the call and the call context.
* `itsInstance` when the current subject is a function will create an instance and use it as the subject for the test block. It accept a `with` options to defines the arguments to pass to the constructor.
* `itsInstance 'property'` when the current subject is a function will create an instance and use the value of the specified property as the subject for the test block.
## Auto-subjects
The `describe` function can be used to specify an implicit subject for test.
```coffeescript
describe AClass, ->
withArguments a, b, c
# subject here is the class constructor function
it -> should exist
# Automatically create an instance with
# the provided parameters as subject
itsInstance -> should exist
# The subject is now AClass.someClassMethod
describe '.someClassMethod', ->
context 'called with some parameters', ->
# subject is now the result
# of calling AClass.someMethod(10)
itsReturn with: [10], -> should equal 20
describe '::someInstanceMethod', ->
context 'called with some parameters', ->
# subject is now the result
# of calling new Aclass(a,b,c).someInstanceMethod('foo')
itsReturn with: ['foo'], -> should equal 'oof'
```
Instance members can also be accessed with a `#` instead of `::`.
## Assertions
Spectacular support two types of assertions, either with the `should` function or with the `expect(...).to` syntax. As addition, the `Object`'s prototype is decorated with a `should` method, allowing to write `10.should equal 10`.
The global `should` function will use the current example subject as actual value to pass to the provided matcher.
```coffeescript
describe 'a number', ->
subject -> 10
it -> should equal 10
it -> @subject.should equal 10
it -> expect(@subject).to equal 10
```
The matcher's description is used as part of the examples description, for instance the following example:
```coffeescript
specify 'the value', -> should equal 10
```
Will produce a description such as:
```
the value should be equal to 10
```
The `expect` function will use the passed-in value as well in the description:
```coffeescript
specify 'the value', -> expect(10).to equal 10
```
Gives:
```
the value 10 should be equal to 10
```
The inverse of `should` is `shouldnt`.
The inverse of `expect(...).to` is `expect(...).not.to`.
### Matchers
The following matchers are provided by Spectacular:
<table cellspacing="0">
<tr>
<td>`exist`</td>
<td>Test if the actual value is neither `null` nor `undefined`.</td>
</tr>
<tr>
<td>`equal(value)`</td>
<td>Performs a comparison between the actual value and the provided one, objects and arrays are compared by their content and not by their identity. Strings, objects and arrays comparison also includes a diff between the two elements in the matcher message.</td>
</tr>
<td>`be(value)`</td>
<td>The `be` matcher have different behavior according to the type of the provided value.
<ul>
<li>If a string is passed, the matcher will look for a property named either `value`, `isValue` or `is_value`, if the property contained a function it will call it, in the end if the value is `true` the match succeed. It is useful to test the state of an object. For instance you can test the resolution of a promise with `@promise.should be 'fulfilled'`
</li>
<li>If the value is an object or an array, the identity of the object is tested using the `===` operator.</li>
<li>Boolean and numeric values are test by value like with the `equal` matcher.</li>
</ul>
</td>
</tr>
<tr>
<td>`beWithin(delta).of(value)`</td>
<td>The `beWithin` matcher checks that the actual is within a delta of your expected value. This is helpful when normal equality expectations do not work well for floating point values.
<ul><li>`pi.should beWithin(0.1).of(3.14)`</li></ul>
</td>
</tr>
<tr>
<td>`have(count, property)`</td>
<td>The `have` matcher will behave differently according the type of the actual value:
<ul>
<li>If the value is a string, the matcher will test for the length of the string, the `property` argument will then be used as a description but can be omitted.</li>
<li>If the value is an array, the matcher will test the length of the array, the `property` argument will then be used as a description but can be omitted.</li>
<li>If the value is an object, the matcher will test the length of an array stored in the `property` property of this object. In that case `property` is mandatory.</li>
<li>For any other type the matcher will fail.</li>
</ul>
</td>
</tr>
<tr>
<td>`haveProperty(property, value)`</td>
<td>Test if the current subject have a `property` whose value match `value`, the `value` can be a matcher such as `object.should haveProperty 'foo', equal 20`.</td>
</tr>
<tr>
<td>`haveAttribute(attribute, value)`</td>
<td>Test if the current subject have an `attribute` whose value match `value`, the `value can be a matcher such as `node.should haveAttribute 'foo', equal 20`.</td>
</tr>
<tr>
<td>`haveClass(cls)`</td>
<td>Test if the current subject have the given `cls` in its `class`attribute.</td>
</tr>
<tr>
<td>`haveSelector(selector)`</td>
<td>Will test a `Node` or a `NodeList` with the given CSS query.</td>
</tr>
<tr>
<td>`match(expression)`</td>
<td>Will either test a regexp or a string against a string or a DOM expression against a node or a node list.
<ul>
<li>`'string'.should match /string/`</li>
<li>`'string'.should match 'string'`</li>
<li>`node.should match @domExpression`</li>
</ul>
</td>
</tr>
<tr>
<td>`contains(element)`</td>
<td>Will either test the presence of `element` in an array or a dom expression in a node or a node list.</td>
<tr>
<td>`throwAnError(message)`</td>
<td>
When the subject is a function it will test that the function throw an error. The `message` argument will be used to test the error message. If no message is passed only the throw of an error is tested.
The `throwAnError` matcher provides additional methods to specify the arguments and context of the call:
<ul>
<li>`throwAnError(message).with(arguments...)`: Sets the arguments to use for the call and return the matcher.</li>
<li>`throwAnError(message).inContext(context)`: Sets the context of the call and return the matcher.</li>
</ul>
</td>
</tr>
<tr>
<td>`haveBeenCalled`</td>
<td>
When the subject is a spy it will test for previous calls on this spy.
The arguments passed to the spy can be tested with the following syntax:
<ul><li>`haveBeenCalled.with(arguments...)`:</li></ul>
</td>
</tr>
</table>
## Before & After Hooks
Before and after hooks are defined in a per context basis.
```coffeescript
describe 'context with hooks', ->
before -> fs.writeFileSync 'path', 'content'
after -> fs.unlink 'path'
specify 'an example', ->
fs.readFileSync('path').should exist
```
Hooks can be asynchronous in the same way as example:
```coffeescript
describe 'context with hooks', ->
before (async) -> fs.writeFile 'path', 'content', (err) ->
return async.reject err if err?
async.resolve()
after (async) ->
fs.unlink 'path'
async.resolve()
specify 'an example', (async) ->
fs.readFile 'path', (err, content) ->
return async.reject err if err?
content.should exist
async.resolve()
```
The same rules apply for hooks than for examples, meaning that a rejected promise end with the example marked as `errored`, and the timeout can be changed using the `rejectAfter` method of the promise.