spectacular
Version:
Advanced BDD framework for CoffeeScript and JavaScript
226 lines (169 loc) • 8.77 kB
Markdown
---
title: Extending Spectacular
date: 2013-07-17 20:26
author: Cédric Néhémie <cedric.nehemie@gmail.com>
template: page.jade
----
## Snake Case Syntax
All the exposed methods are provided both with `camelCase` and `snake_case` syntax. By convention, JavaScript use the camel case form, but some people writing CoffeeScript for nodejs often use the snake case form. Spectacular support both.
You can define any matcher, or helper, or aliases, with either the snake case
or camel case form, the alternative will be also added to the global object.
For instance, the `sharedExample` is also available through `shared_example`.
You can find below a table with all the snake case equivalent:
<table cellspacing="0">
<tr><td>`after`</td><td>No differences</td></tr>
<tr><td>`before`</td><td>No differences</td></tr>
<tr><td>`beWithin`</td><td>`be_within`<td></tr>
<tr><td>`chain`</td><td>No differences</td></tr>
<tr><td>`contains`</td><td>No differences</td></tr>
<tr><td>`context`</td><td>No differences</td></tr>
<tr><td>`createWith`</td><td>`create_with`</td></tr>
<tr><td>`dependsOn`</td><td>`depends_on`</td></tr>
<tr><td>`describe`</td><td>No differences</td></tr>
<tr><td>`description`</td><td>No differences</td></tr>
<tr><td>`equal`</td><td>No differences</td></tr>
<tr><td>`exist`</td><td>No differences</td></tr>
<tr><td>`expect`</td><td>No differences</td></tr>
<tr><td>`factory`</td><td>No differences</td></tr>
<tr><td>`factoryMixin`</td><td>`factory_mixin`</td></tr>
<tr><td>`fail`</td><td>No differences</td></tr>
<tr><td>`failureMessageForShould`</td><td>`failure_message_for_should`</td></tr>
<tr><td>`failureMessageForShouldnt`</td><td>`failure_message_for_shouldnt`</td></tr>
<tr><td>`fixturePath`</td><td>`fixture_path`</td></tr>
<tr><td>`fixtures`</td><td>No differences</td></tr>
<tr><td>`given`</td><td>No differences</td></tr>
<tr><td>`have.selector`</td><td>No differences</td></tr>
<tr><td>`have`</td><td>No differences</td></tr>
<tr><td>`haveBeenCalled.with`</td><td>`have_been_called.with`</td></tr>
<tr><td>`haveBeenCalled`</td><td>`have_been_called`</td></tr>
<tr><td>`it`</td><td>No differences</td></tr>
<tr><td>`itBehavesLike`</td><td>`it_behaves_like`</td></tr>
<tr><td>`its`</td><td>No differences</td></tr>
<tr><td>`itShould`</td><td>`it_should`</td></tr>
<tr><td>`itsInstance`</td><td>`its_instance`</td></tr>
<tr><td>`itsReturn`</td><td>`its_return`</td></tr>
<tr><td>`match`</td><td>No differences</td></tr>
<tr><td>`match`</td><td>No differences</td></tr>
<tr><td>`pending`</td><td>No differences</td></tr>
<tr><td>`registerFixtureHandler`</td><td>`register_fixture_handler`</td></tr>
<tr><td>`set`</td><td>No differences</td></tr>
<tr><td>`should`</td><td>No differences</td></tr>
<tr><td>`shouldnt`</td><td>No differences</td></tr>
<tr><td>`skip`</td><td>No differences</td></tr>
<tr><td>`specify`</td><td>No differences</td></tr>
<tr><td>`spy.argsForCall`</td><td>`spy.args_for_call`</td></tr>
<tr><td>`spyOn(...).andCallFake`</td><td>`spy_on(...).and_call_fake`</td></tr>
<tr><td>`spyOn(...).andCallThrough`</td><td>`spy_on(...).and_call_trough`</td></tr>
<tr><td>`spyOn(...).andReturns`</td><td>`spy_on(...).and_returns`</td></tr>
<tr><td>`spyOn`</td><td>`spy_on`</td></tr>
<tr><td>`subject`</td><td>No differences</td></tr>
<tr><td>`success`</td><td>No differences</td></tr>
<tr><td>`takes`</td><td>No differences</td></tr>
<tr><td>`the`</td><td>No differences</td></tr>
<tr><td>`throwAnError(msg).inContext`</td><td>`throw_an_error(msg).in_context`</td></tr>
<tr><td>`throwAnError(msg).with`</td><td>`throw_an_error(msg).with`</td></tr>
<tr><td>`throwAnError`</td><td>`throw_an_error`</td></tr>
<tr><td>`trait`</td><td>No differences</td></tr>
<tr><td>`whenPass`</td><td>`when_pass`</td></tr>
<tr><td>`withArguments`</td><td>`with_arguments`</td></tr>
<tr><td>`withParameters`</td><td>`with_parameters`</td></tr>
<tr><td>`xcontext`</td><td>No differences</td></tr>
<tr><td>`xdescribe`</td><td>No differences</td></tr>
<tr><td>`xit`</td><td>No differences</td></tr>
</table>
You can also snakify/camelize your own objects using the `utils.snakify` or `utils.camelize` methods:
```coffeescript
myObject =
someMethod: ->
someOtherMethod: ->
utils.snakify myObject
```
Will gives you an object such as:
```coffeescript
myObject =
someMethod: ->
someOtherMethod: ->
some_method: ->
some_other_method: ->
```
<aside>
<p>Note that when using the `spectacular.matcher` and `spectacular.helper` methods the defined matcher/helper is automatically converted to either its camel or snake case alternative.</p>
</aside>
## Matchers
Matchers are defined with the `spectacular.matcher` function. It generates an object with a `match` method that should return a boolean value corresponding to the assertion result.
```coffeescript
spectacular.matcher 'returnSomething', ->
match (actual) -> actual() isnt null
description -> "return something"
failureMessageForShould message = "Expected #{@actual} to #{@description}"
# Usage:
it -> should returnSomething
```
The matcher receive a string containing `' not'` if the matcher was passed to `shouldnt` or `expect(...).not.to`.
If an exception is raised during the matcher execution, the example will be marked as `errored` instead of `failure`.
You can create parameterizable matcher by calling the `takes` function in the matcher definition block.
```coffeescript
spectacular.matcher 'parameterizableMatcher', ->
takes 'value1', 'value2'
match -> @value1 and @value2
description -> 'parameterizableMatcher description'
failureMessageForShould -> 'parameterizableMatcher message'
# Usage:
it -> should parameterizableMatcher(value1, value2)
```
The parameters defined with takes are then stored in the matcher instance with the provided names. The `takes` function accept a slat argument such as `values...`. In that case, the splat must be the sole argument.
<aside>
<p>**Note:** Matchers that doesn't takes arguments are created only once and
then passed to the expectation, they should never stores anything that may induce false positive in later tests.</p>
<p>This is not an issue with parameterizable matchers since an instance is created every time the matcher function is called.</p>
</aside>
It's possible to run code on the initialization of a matcher:
```coffeescript
spectacular.matcher 'matcherWithInit', ->
init -> # do some setup such creating composed objects
match (actual) -> @composedObject.match actual
description -> 'matcher with init description'
failureMessageForShould -> 'matcher with init message'
# Usage:
it -> should matcherWithInit(value1, value2)
```
You can also add chaining methods with the `chain` function:
```coffeescript
spectacular.matcher 'chainableMatcher', ->
takes 'value1'
chain 'with', (@value2) ->
match -> @value1 and @value2
description -> 'chainableMatcher description'
failureMessageForShould -> 'chainableMatcher message'
# Usage:
it -> should chainableMatcher(value1).with(value2)
```
## Asynchronous Matchers
Matchers can be asynchronous, in that case they should return a promise instead of a boolean value. The default timeout for asynchronous matchers is 5000ms, it can be changed by setting the `timeout` in the matcher block.
```coffeescript
spectacular.matcher 'asyncMatcher', ->
timeout 1000
match (actual, notText) ->
promise = new spectacular.Promise
setTimeout (-> promise.resolve actual isnt null), 100
promise
description -> 'should match asynchronously'
failureMessageForShould -> 'Expected to match asynchronously'
```
If the promise is rejected, the example is marked as `errored`.
<aside>
<p>**Note:** Asynchronous matcher should be used carefully and never meddled with synchronous code, a good example of case to avoid can be found below</p>
<pre class='coffeescript'>`specify 'several assertions in an example', ->
expect(object).to anAsynchronousMatcher 'foo', 'bar'
object.foo = 'baz'
expect(object).to anAsynchronousMatcher 'foo', 'baz'`</pre>
<p>In that case the object property's value may have changed when the test is performed by the matcher.</p>
</aside>
## Fixtures Handlers
Spectacular support by default 3 fixtures format: `json`, `html` and `dom`.
However you can quickly extend Spectacular with new fixtures handlers using the `registerFixtureHandler`:
```coffeescript
registerFixtureHandler 'ext', (content) ->
# do something with the content
```
The value returned by the handler block will be used as the fixture in specs.