Ruby Other
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
bin Updated generate_requires script. Mar 27, 2018
example Restructure the gem using lib-opal and spec-opal Mar 12, 2018
exe Fix the -I CLI option Mar 12, 2018
gemfiles Add 0.11 to the test matrix & skip broken specs May 5, 2018
lib-opal Moved opal_filter.rb to spec/rspec/shared. Mar 27, 2018
lib Bump version Jul 23, 2018
rspec-core Skip some more specs for Opal v0.11.1 Jul 17, 2018
rspec-expectations Skip some more specs for Opal v0.11.1 Jul 17, 2018
rspec-mocks Use separated spec output for each RSpec gem Jul 17, 2018
rspec-support Use separated spec output for each RSpec gem Jul 17, 2018
rspec Moved RSpec source under rspec/upstream. Mar 27, 2018
spec-opal-passing Disable all specs except those under scrutiny Mar 12, 2018
spec-opal-rspec Restructure the gem using lib-opal and spec-opal Mar 12, 2018
spec-opal Restructure the gem using lib-opal and spec-opal Mar 12, 2018
spec master: [snapshot] Jul 19, 2018
stubs Improve rspec specs coverage Mar 12, 2018
tasks Split the Rakefile by topic, comment broken tasks Jul 17, 2018
vendor Add support for PhantomJS v2 Nov 2, 2015
.codeclimate.yml add code climate config [skip ci] Jan 15, 2016
.gitignore If tmp/cache files are used when debugging, ignore them Jan 7, 2016
.gitmodules Moved RSpec::Expectations under rspec-expectations/upstream. Mar 27, 2018
.rspec Make arity checking configurable Feb 24, 2016
.travis.yml master: [snapshot] Jul 19, 2018
.yardopts generate docs for client and server Dec 7, 2014
Appraisals Add 0.11 to the test matrix & skip broken specs May 5, 2018
CHANGELOG.md Release v0.7.0.rc.1 Jul 20, 2018
Gemfile Remove opal from Gemfile, we're using Appraisal Jul 17, 2018
README.md Restructure the gem using lib-opal and spec-opal Mar 12, 2018
Rakefile Split the Rakefile by topic, comment broken tasks Jul 17, 2018
config.ru Restructure the gem using lib-opal and spec-opal Mar 12, 2018
opal-rspec.gemspec Recurse submodules when listing gem files Jul 23, 2018

README.md

opal-rspec

Build Status Quality Version

An attempt at a compatibility layer of RSpec for Opal.

For the README for the latest release, click here.

Usage

Add opal-rspec to your Gemfile:

gem 'opal-rspec'

Requirements

Besides what's already reflected in the GEM dependencies:

  • PhantomJS 2.0 or 2.1 for Rake task testing
  • Browser if you want to run and debug tests that way

Run specs in phantomjs

To run specs, a rake task can be added which will load all spec files from spec/:

require 'opal/rspec/rake_task'
Opal::RSpec::RakeTask.new(:default)

Then, to run your specs inside phantomjs (the default runner), just run the rake task:

bundle exec rake

Enable colors in the output

SPEC_OPTS="--color" bundle exec rake

Use a different formatter

SPEC_OPTS="--format json" bundle exec rake

The following formatters have been tested:

  • Default (progress)
  • Documentation
  • JSON

If you need to specify additional requires for a custom formatter, you can do this:

SPEC_OPTS="--format SomeFormatter --require some_formatter" bundle exec rake

You can also customize the pattern of specs used similiar to how RSpec's rake task works:

Opal::RSpec::RakeTask.new(:default) do |server, task|
  # server is an instance of Opal::Server in case you want to add to the load path, customize, etc.
  task.pattern = 'spec_alternate/**/*_spec.rb' # can also supply an array of patterns
  # NOTE: opal-rspec, like rspec, only adds 'spec' to the Opal load path unless you set default_path
  task.default_path = 'spec_alternate'
end

Excluding patterns can be setup this way:

Opal::RSpec::RakeTask.new(:default) do |server, task|
  task.exclude_pattern = 'spec_alternate/**/*_spec.rb' # can also supply an array of patterns
end

FileLists (as in Rake FileLists) can also be supplied:

Opal::RSpec::RakeTask.new(:default) do |server, task|
  task.files = FileList['spec/**/something_spec.rb']
end

PhantomJS will timeout by default after 60 seconds. If you need to lengthen the timeout value, set it like this:

Opal::RSpec::RakeTask.new(:default) do |server, task|
  task.files = FileList['spec/**/something_spec.rb']
  task.timeout = 80000 # 80 seconds, unit needs to be milliseconds
end

Arity checking is enabled by default. Opal allows you to disable arity checking (faster in production this way) but for unit testing, you probably want information on arity mismatch. If you wish to disable it, configure your Rake task like this:

Opal::RSpec::RakeTask.new(:default) do |server, task|
  task.arity_checking = :disabled
end

Run specs in nodejs

Same options as above, you can use the RUNNER=node environment variable or use the Rake task like so:

Opal::RSpec::RakeTask.new(:default) do |server, task|
  task.runner = :node
end

NOTE: nodejs runner does not yet work with source maps or debug mode

Run specs in a browser

opal-rspec can use sprockets to build and serve specs over a simple rack server. Add the following to a config.ru file (see config.ru in this GEM):

require 'opal/rspec'
# or use Opal::RSpec::SprocketsEnvironment.new(spec_pattern='spec-opal/**/*_spec.{rb,opal}') to customize the pattern
sprockets_env = Opal::RSpec::SprocketsEnvironment.new
run Opal::Server.new(sprockets: sprockets_env) { |s|
  s.main = 'opal/rspec/sprockets_runner'
  sprockets_env.add_spec_paths_to_sprockets
  s.debug = false
}

Then run the rack server bundle exec rackup and visit http://localhost:9292 in any web browser.

A new feature as of opal-rspec 0.5 allows you to click a 'Console' button in the browser's test results and get a clickable stack trace in the browser console. This should ease debugging with long, concatenated script files and trying to navigate to where an exception occurred.

Async examples

opal-rspec adds support for async specs to rspec. These specs can be defined using 2 approaches:

  1. Promises returned from subject or the #it block (preferred)
  2. #async instead of #it (in use with opal-rspec <= 0.4.3)

Promise approach

describe MyClass do
  # normal example
  it 'does something' do
    expect(:foo).to eq(:foo)
  end

  # async example
  it 'does something else, too' do
    promise = Promise.new
    delay 1 do
      expect(:foo).to eq(:foo)
      promise.resolve
    end
    promise
  end

  it 'does another thing' do
    # Argument is number of seconds, delay_with_promise is a convenience method that will
    # call setTimeout with the block and return a promise
    delay_with_promise 0 do
      expect(:foo).to eq(:foo)
    end
  end
end

describe MyClass2 do
  # will wait for the before promise to complete before proceeding
  before do
    delay_with_promise 0 do
      puts 'async before 'action
    end
  end

  # async subject works too
  subject do
    delay_with_promise 0 do
      42
    end
  end

  it { is_expected.to eq 42 }

  # If you use an around block and have async specs, you must use this approach
  around do |example|
    puts 'do stuff before'
    example.run.then do
      puts 'do stuff after example'
    end
  end
end

Advantages:

  • Assuming your subject under test (or matchers) return/use promises, the syntax is the same for sync or async specs

Limitations (apply to both async approaches):

  • Right now, async before(:context) and after(:context) hooks cannot be async
  • You cannot use an around hooks on any example where before(:each)/after(:each) hooks are async or with an async implicit subject
  • let dependencies cannot be async, only subject
  • Opal-rspec will not timeout while waiting for your async code to finish

Async/it approach

This is the approach that was supported in opal-rspec <= 0.4.3 and it still works.

describe MyClass2 do
  async 'HTTP requests should work' do
    HTTP.get('/users/1.json') do |res|
      async {
        expect(res).to be_ok
      }
    end
  end
end

The block passed to the second async call informs the runner that this spec is finished so it can move on. Any failures/expectations run inside this block will be run in the context of the example.

Advantages:

  • Hides promises from the specs

Disadvantages:

  • Requires different syntax for async specs vs. sync specs

Opal load path

NOTE: Only the 'spec' directory will be added to the Opal load path by default. Use the Rake task's default_path setting to change that. Here's an example of that.

Opal::RSpec::RakeTask.new do |server, task|
  task.default_path = 'spec/javascripts'
end

If you need to add additional load paths to run your specs, then use the append_path method like this:

Opal::RSpec::RakeTask.new do |server, task|
  server.append_path 'some_path'
end

Other Limitations/Known Issues

80%+ of the RSpec test suites pass so most items work but there are a few things that do not yet work.

  • Core Examples
    • Example groups included like this are currently not working:
module TestMod
  def self.included(base)
    base.class_eval do
      describe 'foo' do
      ...
      end
    end
  end
end

RSpec.configure do |c|
  c.include TestMod
end
  • Formatting/Reporting
    • Specs will not have file path/line number information on them unless they are supplied from user metadata or they fail, see this issue
    • In Firefox w/ the browser runner, no backtraces show up with failed specs
    • Diffs are not yet available when objects do not meet expectations (diff-lcs gem dependency has not been dealt with yet in Opal)
  • Configuration
    • Not all RSpec runner options are supported yet
    • At some point, using node + Phantom's ability to read environment variables could be combined with a opal friendly optparse implementation to allow full options to be supplied/parsed
    • Expect and should syntax are both enabled. They cannot be disabled due to past bugs with the undef keyword in Opal. Status of changing this via config has not been retested.
    • Random order does not work yet due to lack of srand/Random support and RSpec's bundled Random implementation, RSpec::Core::Backports::Random, locks the browser/Phantom. If you specify random order, it will be ignored.
  • Nodejs runner
  • Matchers
    • predicate matchers (be_some_method_on_your_subject) do not currently work with delegate objects (Opal DelegateClass is incomplete)
    • equal and eq matchers function largely the same right now since == and equal? in Opal are largely the same
    • time based matching is not yet tested
    • Due to some issues with splats and arity in Opal, respond_to matchers may not work properly on methods with splats
  • Mocks
    • allow_any_instance/any_instance_of/any_instance are unstable and may cause runner to crash due to issues with redefining the === operator, which breaks a case statement inside Hooks#find_hook
    • using expect/allow on String, Number, or any immutable bridged/native class, does not work since rspec-mocks uses singleton classes and those cannot be defined on immutable objects
    • mocking class methods (including ::new) is currently broken
    • class_double/class_spy are not supported (it depends on ClassVerifyingDouble inheriting from Module to support transferring nested constants, but that doesn't work on Opal)
    • object_spy is not supported (depends on proper initializer behavior in ObjectVerifyingDoubleMethods)
    • verifying partial doubles do not fully work yet (arity issues with Opal)
    • chaining and_return after do...end does not work
    • duck_type argument matching is still buggy
    • RSpec's marshal support does not yet work with Opal's marshaller (so patch_marshal_to_support_partial_doubles config setting is not supported)

Contributing

Install required gems at required versions:

$ bundle install

opal-rspec uses a bundled copy of rspec to fix the areas where opal cannot handle certain features of rspec. To build that file, which is needed to run specs, use:

$ git submodule update --init

When updating the RSpec versions, after updating the submodule revisions, you may need to use the generate_requires Rake task in order to pre-resolve RSpec's dynamic requires

License

(The MIT License)

Copyright (C) 2015 by Brady Wied Copyright (C) 2013 by Adam Beynon

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.