Tests Tools

Spies & Mocks

Spies and mocks can be created in the same way as in Jasmine:

before ->
  # Intercepts the returned value of the spied method call and passed
  # it to the provided method
  spy = spyOn(object, 'method').andCallThrough (result) -> result

  # Mock the call to the method with another one.
  spy = spyOn(object, 'method').andCallFake ->

  # Mock the call to the method by returning the given value
  spy = spyOn(object, 'method').andReturns(value)

Factories

Spectacular provides native factories in a FactoryGirl manner:

class User
  constructor: (@id) ->

factory 'user', class: User, ->
  createWith -> [ Math.floor Math.random() * 100000 ]

  set 'name', -> 'John Doe'

  trait 'with_bike', ->
    set 'bike', -> {model: 'z750', brand: 'Kawasaki'}

user = create 'user', 'with_bike'
# {id: 12345, name: 'John Doe', bike: {model: 'z750', brand: 'Kawasaki'}}

Factories can be reopened any time to add traits or new configuration:

factory 'user', ->
  set 'age', 32

user = create 'user'
# {id: 12345, name: 'John Doe', age: 32}

Factories can also extends another factory with the extends option:

factory 'admin', extends: 'user', ->
  set 'roles', -> ['admin']

user = create 'admin'
# {id: 12345, name: 'John Doe', roles: ['admin']}

Factory Mixins

Factories, as classes, can includes mixins. In that context a mixin is a function executed on a given factory to defines traits and property for this factory.

factoryMixin 'has parent', (factory) ->
  set 'parent', null

  trait 'with parent', ->
    set 'parent', -> create factory.name

factory 'category', class: Object, ->
  include 'has parent'

  set 'name', -> Faker.Lorem.sentence()

category = create 'category', 'with parent'
# {
#   name: 'Ut consectetur sed nihil vel dolores qui qui assumenda.'
#   parent: {
#     name: 'Sunt vitae maiores sit.'
#   }
# }

Factory Hooks

Using the after method you can set a block to be executed after the object was instanciated and all traits have been applied:

factory 'object', class: Object, ->
  set 'field', 'value'

  after 'build', (object) ->
    object.propertiesCount = Object.keys(object).length

Currently build is the only hook available on a factory.

Customize Factory Builds

If you're not happy with the way Spectacular instanciate objects, or that what you're building can't be instanciated through the new operator, you can override the factory build process using the build function.

For instance, given that we have models that may be created through a create method, wa can construct a factory as such:

factory 'my_model', class: MyModel, ->
  build (cls, args) -> cls.create.apply cls, args

  # ...

In that case the create method will use the provided build block instead of the global spectacular.factories.build method.

Build blocks are inherited from a parent factory and can be overriden in traits or in child factories.

Factory Functions

Find below more details about the factory functions:

factory The factory method registers a factory, it takes an option object that set the constructor function to use.
factoryMixin The factoryMixin method registers a factory mixin, it takes the mixin name and a block to execute when included in a factory.
createWith The createWith method defines the arguments to pass to the constructor. It can be either a list of values or a function that will return these arguments. In the case a function is passed, the function will be executed in the context of the current example. The createWith method can be used either in the factory block or in a trait block. When using several trait defining constructor arguments only the last trait will be effective.
set The set method defines a value to set on the specified property, it can takes either a value or a function. In case of a function is passed, the function will be executed in the context of the instance.
trait The trait method registers a trait for this factory. A trait can redefines the arguments to pass to the constructor.
build The build method allow to redefine the build function for this factory.
after The after method takes the name of a hook and a block to execute during that hook. Currently only the
include The include method takes one or more string containing mixins's names.

Fixtures

Fixtures are files that will be loaded before a test execution. Fixture files can be processed before being stored in the context if its extension matches one of the processor defined in the environment.

describe 'with a fixture', ->
  # will be loaded, parsed and stored in @fixture
  fixture 'sample.json'

  # will be loaded, injected in a DOM (either jsdom on node
  # or in a #fixtures div in a browser) and stored in @html
  fixture 'sample.html', as: 'html'

  # will be loaded, parsed to create a DOMExpression
  # and stored in @dom
  fixture 'sample.dom', as: 'dom'

  #...

In practice, a fixture is a before block that load the file, pass it to a processor (if any) and stored in a property in the context.

DOM Expression

Spectacular provides a tool to perform deep test over a DOM structure called DOMExpression, it can be created either using fixtures with the .dom extensions or using the spectacular.dom.DOMExpression class.

A DOM expression is basically a tree of css query strings that describe an HTML structure. For instance a typical webpage can be represented with:

html
  head
  body

DOM expressions use the querySelectorAll method to perform this test. First it will look for the html query and then perform the head and body queries on the results.

It's also possible to test the text content of a node using quote or regex literal in the expression.

#section
  article
    h3
      'article title'
    p
      /article(\s+content)*/

These expressions, when parsed, can be passed to the match or contains matchers and can be used with both nodes and nodes lists.

specify 'the page', ->
  document.should contains @domExpression

specify 'the node', ->
  # on node
  document.querySelector('div').should match @domExpression

  # on nodes list
  document.querySelectorAll('div').should match @domExpression

Shared Example

Shared example are groups of tests that can be used to test similar functionalities accross several classes. For instance the following shared examples test that an object behave like a collection as defined by the spectacular.HasCollection mixin:

sharedExample 'a collection', (options) ->
  {singular, plural} = options
  capitalizedSingular = spectacular.utils.capitalize singular

  context 'adding an item', ->
    given 'item', -> {}
    given 'item2', -> {}

    before -> @subject["add#{capitalizedSingular}"] @item

    specify 'the collection', ->
      @subject[plural].should contains @item

    specify "then calling has#{capitalizedSingular}", ->
      @subject["has#{capitalizedSingular}"](@item).should be true

    specify 'the item index', ->
      @subject["find#{capitalizedSingular}"](@item).should equal 0

    specify 'the item at index 0', ->
      @subject["#{singular}At"](0).should be @item

    context 'then removing it', ->
      before -> @subject["remove#{capitalizedSingular}"] @item

      specify 'the collection', ->
        @subject[plural].shouldnt contains @item

    context 'removing an inexistent item', ->
      before -> @subject["remove#{capitalizedSingular}"] @item2

      specify 'the collection', ->
        @subject[plural].should contains @item

The shared example can then be called with either the itBehavesLike or itShould functions:

describe ClassWithCollection, ->
  subject -> new ClassWithCollection

  itBehavesLike 'a collection', {
    singular: 'child'
    plural: 'children'
  }

Tests Helpers

Helpers can be created and exposed with the spectacular.helper function. The function takes a name and a value and will expose it on the global object through a GlobalizableObject.

spectacular.helper 'someHelper', (params...) ->
  # Your helper's code

# Usage
someHelper(SomeClass, someOption: 'some value')