Teaspoon is a Javascript test runner built for Rails. It can run tests in the browser and headless using PhantomJS, Selenium WebDriver, or Capybara Webkit.
The project goal is to stay simple while also providing the most complete Javascript testing solution for Rails.
Teaspoon takes advantage of the asset pipeline, and ships with support for Jasmine, Mocha, and QUnit.
Feedback, ideas and pull requests are always welcome, or you can hit us up on Twitter @modeset_.
If you'd like to use Teaspoon with Guard, check out the guard-teaspoon project. Or, if you want to use the Spring preloader, use the unofficial spring-commands-teaspoon.
Running in the console using Tapout
- Installation
- Usage
- Writing Specs
- Fixtures
- Suites
- Coverage
- Configuration
- Test Frameworks
- Support Libraries
- CI Support
Add it to your Gemfile. In most cases you'll want to restrict it to the :development, :test
groups.
group :development, :test do
gem "teaspoon"
end
Run the install generator to get the environment file and a basic spec helper. You can tell the generator which framework you want to use, or if you want a CoffeeScript spec helper. Run the install generator with the --help
flag for a list of available options.
rails generate teaspoon:install --framework=mocha --coffee
To run Teaspoon headless you'll need PhantomJS, Selenium Webdriver or Capybara Webkit. We recommend PhantomJS, which you can install with homebrew, npm or as a download.
We made some changes to how configuration and loading works for version 0.8.0, which might cause some confusion. For this we're sorry, but it'll be better in the long run. While we know that considerable changes like these can be a pain, they're not made frivolously, and they set the groundwork for what we can all build on and contribute to. We appreciate your tolerance and willingness to help us fix anything that we missed.
❤️
- backup your
spec/teaspoon_env.rb
file. - run the install generator to get the new
teaspoon_env.rb
. - migrate your old settings into the new file, noting the changes that might exist.
- move all settings that you had in
config/initializers/teaspoon.rb
intospec/teaspoon_env.rb
and delete the initializer.
Teaspoon uses the Rails asset pipeline to serve files. This allows you to use = require
in your test files, and allows you use things like HAML or RABL/JBuilder within your fixtures.
Here's a great Quick Start Walkthrough for writing and running your first tests.
You can run Teaspoon three ways -- in the browser, via the rake task, and using the command line interface (CLI).
http://localhost:3000/teaspoon
rake teaspoon
The rake task provides several ways of focusing tests. You can specify the suite to run, the files to run, directories to run, etc.
rake teaspoon suite=my_fantastic_suite
rake teaspoon files=spec/javascripts/integration,spec/javascripts/calculator_spec.js
bundle exec teaspoon
The CLI also provides several ways of focusing tests. You can specify the suite to run, the files to run, directories to run, filters, etc.
bundle exec teaspoon --suite=my_fantastic_suite
bundle exec teaspoon spec/javascripts/integration spec/javascripts/calculator_spec.js
bundle exec teaspoon --filter="Calculator should add two digits"
Get full command line help:
bundle exec teaspoon --help
Note: The rake task and CLI run within the development environment unless otherwise specified.
Depending on which framework you use this can differ, and there's an expectation that you have a certain level of familiarity with your chosen test framework.
Teaspoon supports Jasmine 1.3, Mocha and QUnit. And since it's possible to use the asset pipeline, feel free to use the = require
directive throughout your specs and spec helpers.
Here's a basic spec written in Javascript using Jasmine:
//= require jquery
describe("My great feature", function() {
it("will change the world", function() {
expect(true).toBe(true);
expect(jQuery).toBeDefined();
});
});
You can also check out the examples of a Mocha Spec, and a QUnit Test.
Every test framework is different, but we've tried to normalize some of those differences. For instance, Jasmine lacks the concept pending, while Mocha provides several ways to achieve this. So we thought it would be worth defining what is standard between the two frameworks. QUnit doesn't easily support the concept of pending, so that's not covered.
To mark a spec as pending in both Mocha and Jasmine, you can either not provide a function as the second argument to the it
call, or you can use xit
and xdescribe
.
describe("My great feature", function() {
it("hasn't been tested yet");
xit("has a test I can't figure out", function() {
expect("complexity").to.be("easily testable");
});
xdescribe("A whole section that I've not gotten to", function() {
it("hasn't been tested yet", function() {
expect(true).to.be(false);
});
});
});
Teaspoon allows deferring execution, which can be useful for asynchronous execution.
Teaspoon.defer = true;
setTimeout(Teaspoon.execute, 1000); // defers execution for 1 second as an example
You can configure your suite to boot with require.js by setting the suite boot_partial
directive to "boot_require_js"
.
Be sure to require require.js
in your spec helper. Teaspoon doesn't include it as a support library, so you'll need to provide your own.
//= require require
Now require.js will be used to load all the specs in your suite, however, you'll still need to use require.js to pull down the dependencies as you would normally.
define(['Model'], function (Model) {
describe('Model', function () {
// ...
});
});
Teaspoon ships with a fixture library that works with Jasmine, Mocha, and QUnit with a minimum of effort, has a nice consistent API, and isn't dependent on jQuery.
The fixture path is configurable within Teaspoon, and the views will be rendered by a standard controller. This allows you to use things like RABL/JBuilder if you're building JSON, or HAML if you're building markup.
Loading fixtures allows you to specify any number of files to load, and if they should be appended to the fixture element or replace what's currently there.
fixture.load(url[, url, ...], append = false)
or fixture(url[, url, ...], append = false)
If you don't want to load files directly from the server you can provide strings instead of files, otherwise behaves like load.
fixture.set(html[, html, ...], append = false)
You shouldn't have to cleanup (we do that for you based on your test framework), but if you need it.
fixture.cleanup()
Some test cases require stubbing Ajax requests, and in those cases you may want to preload the fixture files to cache them for later. You can preload fixtures in your spec helper, or before you start mocking Ajax methods.
fixture.preload(url[, url, ...])
fixture.preload("fixture.html", "fixture.json"); // make the actual requests for the files
describe("Using fixtures", function() {
fixture.set("<h2>Another Title</h2>"); // create some markup manually (will be in a beforeEach)
beforeEach(function() {
this.fixtures = fixture.load("fixture.html", "fixture.json", true); // append these fixtures which were already cached
});
it("loads fixtures", function() {
expect($("h1", fixture.el).text()).toBe("Title") // using fixture.el as a jquery scope
expect($("h2", fixture.el).text()).toBe("Another Title")
expect(this.fixtures[0]).toBe(fixture.el) // the element is available as a return value and through fixture.el
expect(this.fixtures[1]).toEqual(fixture.json[0]) // the json for json fixtures is returned, and available in fixture.json
});
});
Check out some example of using fixtures with Mocha, QUnit.
Note: The element that Teaspoon creates is "#teaspoon-fixtures", in case you need to access it directly.
Teaspoon uses the concept of suites to group tests at a high level. These suites run in isolation and can have different configurations.
A default suite has been generated for you in your teaspoon_env.rb
.
Suites inherit from a "default" suite. To modify the "default" suite simply don't specify a name for the suite. In this example we're configuring the default suite, which all other suites will inherit from.
config.suite do |suite|
suite.helper = "other_spec_helper.js"
end
When defining a custom suite, provide a name and a block. The following example defines a suite named "my_suite".
config.suite :my_suite do |suite|
suite.helper = "my_spec_helper.js"
end
Hooks are designed to facilitate loading fixtures or other things that might be required on the back end before, after, or during running a suite or test. You can define hooks in your suite by specifying a name and a block. Hooks with the same name will be added to an array, and all will be called when the hook is requested. If you don't specify a name, :default will be assumed.
config.suite :my_suite do |suite|
suite.hook :fixtures do
# some code that would load your fixtures
end
end
You can then use the javascript Teaspoon.hook("fixtures")
call at the beginning of a suite run or similar. All blocks that have been specified for a given hook will be called in the order they were defined.
If your hook accepts arguments:
config.suite :my_suite do |suite|
suite.hook :fixtures do |arguments|
# some code that has access to your passed in arguments
end
end
You can then use the following javascript to call your hook with arguments:
args = JSON.stringify({'hook_args': {'foo': 'bar'}})
Teaspoon.hook('fixtures', { 'method': 'POST', 'payload': args})
*note that you must specify the HTTP verb under the method
key and your argurments are passed as a stringied JSON document keyed by hook_args
under the payload
key
Teaspoon is happy to look for files for you (and this is recommended), but you can disable this feature and maintain a manifest yourself. Configure the suite to not match any files, and then use your spec helper to create your manifest.
config.suite do |suite|
suite.matcher = nil
suite.helper = "spec_manifest"
end
Note: This limits your ability to run specific files from the command line interface and other benefits, and so isn't recommended.
Teaspoon uses Istanbul to generate code coverage statistics and reports. You can define coverage configurations the same way you define suites.
Each suite allows specifying ignored files, which allows you to ignore support libraries and dependencies.
The following configuration and example generates a text and cobertura report -- and an annotated HTML report that you can inspect further.
config.coverage do |coverage|
coverage.reports = ['text', 'html', 'cobertura']
end
bundle exec teaspoon --coverage=default
If you use the "text"
, or "text-summary"
reports, they will be output to the console after the tests have completed.
--------------------+-----------+-----------+-----------+-----------+
File | % Stmts |% Branches | % Funcs | % Lines |
--------------------+-----------+-----------+-----------+-----------+
phantomjs/ | 93.75 | 75 | 94.12 | 93.65 |
runner.js | 93.75 | 75 | 94.12 | 93.65 |
--------------------+-----------+-----------+-----------+-----------+
All files | 93.75 | 75 | 94.12 | 93.65 |
--------------------+-----------+-----------+-----------+-----------+
Teaspoon allows defining coverage threshold requirements. If a threshold is not met, it will cause a test run failure.
This example would cause a failure if less than 50% of the statements were not covered by the tests for instance.
config.coverage :CI do |coverage|
coverage.statements = 50
coverage.functions = 50
coverage.branches = 50
coverage.lines = 50
end
When you install Teaspoon a teaspoon_env.rb
file is generated that contains most of this information, but we've provided it here too.
- mount_at
-
Determines where the Teaspoon routes will be mounted. Changing this to "/jasmine" would allow you to browse to
http://localhost:3000/jasmine
to run your tests.
default:
"/teaspoon"
- root
-
Specifies the root where Teaspoon will look for files. If you're testing an engine using a dummy application it can be useful to set this to your engines root (e.g.
Teaspoon::Engine.root
).
Note: Defaults toRails.root
if nil.
default:
nil
- asset_paths
-
Paths that will be appended to the Rails assets paths.
Note: Relative toconfig.root
.
default:
["spec/javascripts", "spec/javascripts/stylesheets", "test/javascripts", "test/javascripts/stylesheets"]
- fixture_paths
-
Fixtures are rendered through a controller, which allows using HAML, RABL/JBuilder, etc. Files in this path will be rendered as fixtures.
default:
["spec/javascripts/fixtures", "test/javascripts/fixtures"]
- use_framework(name[, version])
-
Specify the framework and optionally version you would like to use. This will do some basic setup for you -- which you can override with the directives below. This should be specified first, as it can override other directives.
Note: If no version is specified, the latest is assumed.
available: jasmine[1.3.1], mocha[1.10.0, 1.17.1] qunit[1.12.0, 1.14.0]
default:[no default]
- matcher
-
Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These files need to be within an asset path. You can add asset paths using the `config.asset_paths`.
Note: Can be set tonil
to match no files.
default:
"{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}"
- helper
-
This suites spec helper, which can require additional support files. This file is loaded before any of your test files are loaded.
default:
"spec_helper"
- javascripts
-
The core Teaspoon javascripts. If you're using the `use_framework` directive this will be set based on that, but it can be useful to provide an override to use a custom version of a test framework.
Note: It's recommended to only include the core files here, as you can require support libraries from your spec helper.
Note: For CoffeeScript files use"teaspoon/jasmine"
etc.
available: teaspoon-jasmine, teaspoon-mocha, teaspoon-qunit
default:["jasmine/1.3.1", "teaspoon-jasmine"]
- stylesheets
-
You can include your own stylesheets if you want to change how Teaspoon looks.
Note: Spec related CSS can and should be loaded using fixtures.
default:
["teaspoon"]
- boot_partial
-
Partial to be rendered in the head tag of the runner. You can use the provided ones or define your own by creating a `_boot.html.erb` in your fixtures path, and adjust the config to `"/boot"` for instance.
available: boot, boot_require_js
default:"boot"
- normalize_asset_path
-
When using custom file-extensions you might need to supply a custom asset path normalization. If you need to match a
custom extension, simply supply a custom lambda/proc that returns the desired filename.
default:
filename.gsub('.erb', '').gsub(/(\.js\.coffee|\.coffee)$/, ".js")
The best way to read about the configuration options is to generate the initializer and env, but we've included the info here as well.
default: "body"
default: [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}]
suite.hook :fixtures, proc{ }
)
default: Hash.new{ |h, k| h[k] = [] }
These configuration directives are applicable only when running via the rake task or command line interface. These directives can be overridden using the command line interface arguments or with ENV variables when using the rake task.
- driver
-
Specify which headless driver to use. Supports PhantomJS, Selenium Webdriver and Capybara Webkit.
Using PhantomJS.
Using Selenium WebDriver
Using Capybara Webkitavailable: phantomjs, selenium, capybara-webkit
default:"phantomjs"
- CLI: -d, --driver DRIVER
- ENV: DRIVER=[DRIVER]
- driver_options
-
Specify additional options/switches for the driver.
Using PhantomJS.
Using Selenium WebDriver
Using Capybara Webkitdefault:
nil
- CLI: --driver-options OPTIONS
- ENV: DRIVER_OPTIONS=[OPTIONS]
- driver_timeout
-
Specify the timeout for the driver. Specs are expected to complete within this time frame or the run will be considered a failure. This is to avoid issues that can arise where tests stall.
default:
180
- CLI: --driver-timeout SECONDS
- ENV: DRIVER_TIMEOUT=[SECONDS]
- server
-
Specify a server to use with Rack (e.g. thin, mongrel). If nil is provided Rack::Server is used.
default:
nil
- CLI: --server SERVER
- ENV: SERVER=[SERVER]
- server_port
-
Specify a port to run on a specific port, otherwise Teaspoon will use a random available port.
default:
nil
- CLI: --server-port PORT
- ENV: SERVER_PORT=[PORT]
- server_timeout
-
Timeout for starting the server in seconds. If your server is slow to start you may have to bump this, or you may want to lower this if you know it shouldn't take long to start.
default:
20
- CLI: --server-timeout SECONDS
- ENV: SERVER_TIMEOUT=[SECONDS]
- fail_fast
-
Force Teaspoon to fail immediately after a failing suite. Can be useful to make Teaspoon fail early if you have several suites, but in environments like CI this may not be desirable.
default:
true
- CLI: -F, --[no-]fail-fast
- ENV: FAIL_FAST=[true/false]
- formatters
-
Specify the formatters to use when outputting the results.
Note: Output files can be specified by using"junit>/path/to/output.xml"
.
available: dot, documentation, clean, json, junit, pride, snowday, swayze_or_oprah, tap, tap_y, teamcity
default:"dot"
- CLI: -f, --format FORMATTERS
- ENV: FORMATTERS=[FORMATTERS]
- color
-
Specify if you want color output from the formatters.
default:
true
- CLI: -c, --[no-]color
- ENV: COLOR=[true/false]
- suppress_log
-
Teaspoon pipes all console[log/debug/error] to $stdout. This is useful to catch places where you've forgotten to remove them, but in verbose applications this may not be desirable.
default:
false
- CLI: -q, --[no-]suppress-log
- ENV: SUPPRESS_LOG=[true/false]
- use_coverage
-
Specify that you always want a coverage configuration to be used.
default:
nil
- CLI: -C, --coverage=CONFIG_NAME
- ENV: USE_COVERAGE=[CONFIG_NAME]
- reports
-
Which coverage reports Istanbul should generate. Correlates directly to what Istanbul supports.
available: text-summary, text, html, lcov, lcovonly, cobertura, teamcity
default:["text-summary", "html"]
- output_path
-
The path that the coverage should be written to - when there's an artifact to write to disk.
Note: Relative toconfig.root
.
default:
"coverage"
- statements
-
Specify a statement threshold. If this coverage threshold isn't met the test run will fail. (0-100) or nil.
default:
nil
- functions
-
Specify a function threshold. If this coverage threshold isn't met the test run will fail. (0-100) or nil.
default:
nil
- branches
-
Specify a branch threshold. If this coverage threshold isn't met the test run will fail. (0-100) or nil.
default:
nil
- lines
-
Specify a line threshold. If this coverage threshold isn't met the test run will fail. (0-100) or nil.
default:
nil
- Sinon.JS (1.8.2) Standalone test spies, stubs and mocks for JavaScript. No dependencies, works with any unit testing framework. BSD Licence.
- ChaiJS (1.8.1) BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework. MIT License.
- Chai-jQ (0.0.7) An alternate plugin for the Chai assertion library to provide jQuery-specific assertions. MIT License.
- Sinon-Chai (1.0.0) Extends Chai with assertions for the Sinon.JS mocking framework. MIT-ish License.
- expect.js (0.1.2) Minimalistic BDD assertion toolkit based on should.js. MIT License.
- jasmine-jquery-1.7.0.js (1.7.0) For Jasmine v1, A set of custom matchers for jQuery, and an API for handling HTML fixtures in your specs. MIT License.
- jasmine-jquery-2.0.0.js (2.0.0) For Jasmine v2, A set of custom matchers for jQuery, and an API for handling HTML fixtures in your specs. MIT License.
Jasmine is used by default unless you specify otherwise. We've been using Jasmine for a long time, and have been pretty happy with it. It lacks a few important things that could be in a test framework, so we've done a little bit of work to make that nicer. Like adding pending spec support.
Mocha came up while we were working on Teaspoon -- we read up about it and thought it was a pretty awesome library with some really great approaches to some of the things that some of us browser types should consider more often, so we included it and added support for it. We encourage you to give it a try. Read more about Using Mocha with Teaspoon.
QUnit We're not sure about how many people use QUnit, but we like jQuery, so we added it. Read more about Using QUnit with Teaspoon.
We know that testing usually requires more than just the test framework, so we've included some of the libraries that we use on a regular basis.
You can require these files in your spec helper by using:
//= require support/sinon
//= require support/chai
//= require support/expect
//= require support/jasmine-jquery-1.7.0
//= require support/jasmine-jquery-2.0.0
Teaspoon works great on CI setups, and we've spent a good amount of time on getting that good. There's a lot of information to go over with that topic, but here are some highlights.
Add a line to execute Teaspoon (e.g. bundle exec teaspoon
) in your CI config file. If you're using TravisCI or CircleCI it just works, but if you're using something else all you should need is to ensure PhantomJS is installed.
Alternately, you can add Teaspoon to the default rake tasks by clearing out the defaults (not always required), and then add :teaspoon
into the chain of tasks where you want.
Rake::Task['default'].prerequisites.clear
Rake::Task['default'].clear
task default: [:spec, :teaspoon, :cucumber]
If you want to generate reports that CI can use you can install Istanbul for coverage reports -- and output the report using the cobertura format, which Hudson and some others can read. You can track spec failure rates by using the tap formatter, or on TeamCity setups you can use the teamcity formatter. A junit formatter is available as well.
We encourage you to experiment and let us know. Feel free to create a wiki article about what you did to get it working on your CI setup.
Some build services also support selenium based setups using Xvfb and Firefox. We've had some success doing this on CircleCI, however others may work just as well. Most of the time it should just work. If you are experiencing timeout's try to add a Post-dependency command to precompile your assets.
rake assets:precompile
Konacha Jasminerice Evergreen jasmine-rails guard-jasmine
Licensed under the MIT License
Copyright 2014 Mode Set
All licenses for the bundled Javascript libraries are included (MIT/BSD).