# JSpec JSpec is a minimalistic JavaScript behavior driven development framework, providing **simple installatio**n, extremely **low learning curve**, absolutely **no pollution** to core prototypes, async request support, and incredibly sexy syntax, tons of matchers and **much more**. ## Features * Highly readable * Framework / DOM independent * Modular via JSpec Module's and hooks * Mock Ajax Requests * Rhino support * Node.js support * Async support * Growl (unobtrustive notifications) support * Fixture support * Ruby JavaScript testing server * Nested describes * Does not pollute core object prototypes * Cascading before/after/before_each/after_each hooks * Extremely simple and intuitive matcher declaration * Over 45 core matchers * Allows parens to be optional when using matchers to increase readability * Several helpful reporters (DOM, Console, Terminal, ...) * Assertion graphs displaying how many, and which assertions pass or failed * Default / customizable evaluation contexts * DOM sandbox support * Great looking default DOM theme * `jspec` command-line utility for auto-running specs, and initializing project templates * Proxy or 'Spy' assertions * Method Stubbing * Shared behaviors * Extend the jspec executable with project / user specific sub-commands. * Profiling * Interactive Shell * Ruby on Rails Integration * Install support projects with a single command (jQuery, Rhino, Prototype, Dojo, etc) * Tiny (2000-ish LOC) ## Companies Using JSpec To add or request removal from this list please email tj@vision-media.ca * [Apple](http://apple.com) * [Google - YouTube](http://youtube.com) * [Palm](http://palm.com) * [Carfax](http://carfax.com) * [Vision Media](http://vision-media.ca) ## Installation Simply download JSpec and include _JSpec.css_ and _JSpec.js_ in your markup. Head over to the downloads section on Github, clone this public repo, or add JSpec as a git submodule with in your project. Alternatively JSpec is also available as a Ruby Gem (though this is not required), which also provides the `jspec` executable. First install [Gemcutter](http://gemcutter.org/) then execute: $ sudo gem install jspec At which point you may: $ jspec init myproject By default, the command above will use absolute path for all JSpec library files. This behavior can be a problem when you're working across different computers or operating systems. You can freeze the library or symlink it. $ jspec init myproject --freeze $ jspec init myproject --symlink JSpec scripts should NOT be referenced via the `
... You may optionally want to use sources in the _/pkg_ directory for your project, since it includes compressed alternatives generated each release. ## Example describe 'ShoppingCart' before_each cart = new ShoppingCart end describe 'addProducts' it 'should add several products' cart.addProduct('cookie') cart.addProduct('icecream') cart.should.have 2, 'products' end end describe 'checkout' it 'should throw an error when checking out with no products' -{ cart.clear().checkout() }.should.throw_error EmptyCart end end end ## Grammar-less Example JSpec's grammar is optional, you may also use the equivalent grammar-less alternative below using pure JavaScript (when using the JSpec grammar you may also use grammar-less assertions): JSpec.describe('ShoppingCart', function(){ before_each(function{ cart = new ShoppingCart }) describe('addProducts', function(){ it ('should add several products', function(){ cart.addProducts('cookie') cart.addProducts('icecream') expect(cart).to(have, 2, 'products') }) }) describe('checkout', function(){ it ('should throw an error when checking out with no products', function(){ expect(function(){ cart.clear().checkout() }).to(throw_error, EmptyCart) }) }) }) ## Options The following options may be passed to _JSpec.run()_. - fixturePath - {string} path to fixture directory (DOM, Terminal, Server) - failuresOnly - {bool} displays only failing specs, making them quick to discover and fix (DOM, Terminal, Server) - reportToId - {string} an element id to report to when using the DOM reporter (DOM) - verbose - {bool} verbose server output, defaults to false (Server) ## Matchers ### Core - equal, be - === - be_a, be_an - have constructor of x - be_an_instance_of - instanceof x - be_at_least - >= - be_at_most - <= - be_null - == null - be_empty - length < 0 or {} - be_true - == true - be_false - == false - be_type - be type of x - be_greater_than - > - be_less_than - < - be_undefined - check if variable passed is undefined - throw_error - should throw an error, optionally supply the error string or regexp for message comparison - have - object should have n of property (person.should.have(2, 'pets')) - have_at_least - object should have at least n of property - have_at_most - object should have a maximum n of property - have_within - object should have within n..n of property (person.should.have_within(1..3, 'pets') - have_length - length of n - have_prop - object should have property x, optionally supplying an expected value - have_property - strict version of have_prop - be_within - checks if n is within the range passed - include - include substring, array element, or hash key - match - string should match regexp x - respond_to - property x should be a function - eql - matches simple literals (strings, numbers) with == However composites like arrays or 'hashes' are recursively matched, meaning that [1, 2, [3]].should_eql([1, 2, [3]]) will be true. ### jQuery - have_tag, have_one - have exactly one tag - have_tags, have_many - have more than one tag - have_child - have exactly one child - have_children - have more than one child - have_text - have plain text - have_attr - have an attribute, with optional value - have_type - have_id - have_title - have_alt - have_href - have_rel - have_rev - have_name - have_target - have_value - have_class - have_classes - be_visible - be_hidden - be_enabled - be_disabled - be_selected - be_checked ## Growl Support JSpec uses the [JavaScript Growl](http://github.com/visionmedia/js-growl) library to provide growl support when using the **Rhino JavaScript engine**. To enable simply `load()` _jspec.growl.js_ within _spec/rhino.js_ ## Async Support With Mock Timers The javascript mock timers library is available at [http://github.com/visionmedia/js-mock-timers](http://github.com/visionmedia/js-mock-timers) although it is already bundled with JSpec at _lib/jspec.timers.js_ Timers return ids and may be passed to `clearInterval()`, however they do not execute in threads, they must be manually scheduled and controlled via the `tick()` function. setTimeout(function(){ alert('Wahoo!') }, 400) tick(200) // Nothing happens tick(400) // Wahoo! `setInterval()` works as expected, although it persists, where as `setTimeout()` is destroyed after a single call. As conveyed by the last `tick()` call below, a large increment in milliseconds may cause the callbacks to be called several times to 'catch up'. progress = '' var id = setInterval(function(){ progress += '.' }, 100) tick(50), print(progress) // '' tick(50), print(progress) // '.' tick(100), print(progress) // '..' tick(100), print(progress) // '...' tick(300), print(progress) // '......' clearInterval(id) tick(800) // Nothing happens You may also reset at any time using resetTimers() ## Proxy Assertions Proxy or 'Spy' assertions allow you to assert that a method is called n number of times, with x arguments, returning x value. For example: person = { getPets : function(species){ return ['izzy'] }} person.should.receive('getPets', 'twice').with_args(an_instance_of(String))and_return(['izzy']) person.getPets('dog') // This will pass person.getPets() // This will fail because we asked an instance of String This is a useful mechanism for testing the behavior of your object, as well as how other methods may interact with it. Below is another example: array = ['foo', 'bar'] array.should.receive('toString').and_return('foo,bar') 'array: ' + array // This line causes the spec to pass due to calling toString() For more examples view _spec/spec.matchers.js_ ## Method Stubbing JSpec currently provides very simple stubbing support shown below: person = { toString : function(){ return 'Foo
') // or html = $('Foo
') ... end it 'should do something' html.should.have_text 'Foo' end end ## Custom Matchers First lets create a simple equality matcher. In the case below JSpec is smart enough to realize this is simply a binary operator, and simply transforms this into `actual === expected` JSpec.addMatchers({ equal : '===' }) To alias a method to keep your specs readable you may alias them like below: JSpec.addMatchers({ be : 'alias equal' }) 'foo'.should.equal 'foo' true.should.be true Matchers with string bodies implicitly return the expression value. The expanded version of the equal matcher would then be: JSpec.addMatchers({ equal : 'actual === expected' }) Large matchers or those which require several parameters may wish to utilize the hash method: JSpec.addMatchers({ equal : { match : function(actual, expected){ return actual === expected }} }) To keep JSpec tiny, JSpec will default to generating failure messages for you, how ever this can be explicitly defined: JSpec.addMatchers({ equal : { match : function(actual, expected){ return actual === expected }, message : function(actual, expected, negate) { return 'a message here' } } }) When defining matchers that are extremely similar in functionality, however require different names, you may use a prefixed list of words like below which defines be_disabled, be_selected, be_checked, and have_type, have_id, etc. Each function must return the matcher body which will be used. JSpec.addMatchers({ 'be disabled selected checked' : function(attr) { return 'jQuery(actual).attr("' + attr + '")' }, 'have type id title alt href src sel rev name target' : function(attr) { return function(actual, value) { return value ? jQuery(actual).attr(attr) ## value: jQuery(actual).attr(attr) } } }) ## Extending Or Hooking Into JSpec JSpec provides a hook architecture for extending or analyzing various points in its execution, through the use of modules. For a module example view _lib/jspec.jquery.js_. The following methods or properties are utilized by JSpec: - name : module name string - init : called to initialize a module - reporters : hash of reporters merged with JSpec.reporters - utilities : hash of utility functions merged with JSpec.defaultContext - matchers : hash of matchers merged with JSpec's core matchers via JSpec.addMatchers() - DSLs : hash of DSL methods; for example DSLs.snake contains before_each, after_each, etc. Where as DSLs.camel may contain beforeEach, afterEach, etc. Below is a list of hooks, descriptions, and valid return values which may simply be implemented as module methods. beforeSuite, afterSuite, beforeSpec, and afterSpec have lower precedence than before_each, after_each etc within the specs themselves, allowing them to override or undo anything that has been done by a Module. - running(options) : started running JSpec with the options passed : returning 'stop' will halt running - loading(file) : loading a file : returning 'stop' will prevent loading - executing(file) : executing a file : returning 'stop' will prevent execution - posting(data, url) : posting data to a url : returning 'stop' will prevent request - preprocessing(input) : before input string is preprocessed : return input string for next hook to preprocess - stubbing(object, method, result) : called when stubbing an object's method, and return value (result). : (no return value) - requiring(dependency, message) : requiring a dependency : (no return value) - beforeAssertion(assertion) : before an assertion has been made : (no return value) - afterAssertion(assertion) : after an assertion has been made : (no return value) - addingMatcher(name, body) : unprocessed matcher name and body : (no return value) - addingSuite(suite) : adding Suite instance to JSpec : (no return value) - beforeSuite(suite) : before running of suite (describe block) : (no return value) - afterSuite(suite) : after running of suite (describe block) : (no return value) - beforeSpec(spec) : before running of spec (it block) : (no return value) - afterSpec(spec) : after running of spec (it block) : (no return value) - reporting(options) : called before reporting : (no return value) - evaluatingBody(dsl, matchers, context, contents) : evaluating body contents, with the given context, matchers and dsl. : (no return value) For example you may wish to proxy files which are being executed, simply implement the executing method like below. This example will stop execution of any file matching /matchers/. MyModule = { executing : function(file) { if (file.match(/matchers/)) return 'stop' } } JSpec.include(MyModule) Immutable values may also be passed to hooks using hookImmutable() internally. This allows for simple numbers, strings, etc to be utilized or altered within a hook implementation. Below is an example module which adds functionality to the JSpec grammar by converting `SomeObject.stub('method')` to `stub(SomeObject, 'method')`: JSpec.include({ preprocessing : function(input) { return input.replace(/(\w+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)') } }) ## JSpec Command-line Utility When installed as a Ruby Gem, the `jspec` executable will become available, allowing you to initialize project templates quickly, as well as auto-testing specifications when a file is altered. Initialize JSpec-driven project template in directory _myproject_: $ jspec init myproject Once within 'myproject' start testing by executing: $ jspec For additional usage execute: $ jspec help Or for specific usage: $ jspec help run ## Extending JSpec's Executable Both project specific, and user specific sub-commands may be used to extend those already provided by `jspec`. For example create the following in spec/commands/example_command.rb which are loaded when `jspec` is executed. command :example do |c| c.syntax = 'jspec example [options]' c.description = 'Just an example command' c.option '-f', '--foo string', 'Does some foo with