UNPKG

spectacular

Version:

Advanced BDD framework for CoffeeScript and JavaScript

342 lines (255 loc) 12.2 kB
--- 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.