├── .gitignore ├── .travis.yml ├── Gemfile ├── README.md ├── Rakefile ├── gemfiles ├── rspec_2_10_0.Gemfile ├── rspec_2_14_1.Gemfile ├── rspec_3_0_0.Gemfile └── rspec_latest.Gemfile ├── lib ├── rspec-console.rb └── rspec-console │ ├── config_cache.rb │ ├── pry.rb │ ├── rspec_state.rb │ └── runner.rb ├── rspec-console.gemspec ├── spec ├── fail_spec.rb ├── helper_spec.rb ├── shared_examples_spec.rb ├── simple_spec.rb └── spec_helper.rb └── test ├── integration └── rspec_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | # Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | Gemfile.lock 38 | gemfiles/*.lock 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2 4 | script: bundle exec rake test 5 | notifications: 6 | email: 7 | on_success: never 8 | on_failure: change 9 | gemfile: 10 | - gemfiles/rspec_2_10_0.Gemfile 11 | - gemfiles/rspec_2_14_1.Gemfile 12 | - gemfiles/rspec_3_0_0.Gemfile 13 | - gemfiles/rspec_latest.Gemfile 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | 4 | gem 'rspec' 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RSpec Console [![Build Status](https://travis-ci.org/nviennot/rspec-console.svg)](https://travis-ci.org/nviennot/rspec-console) 2 | ============= 3 | 4 | RSpec Console allows you to run your RSpec tests in a Rails console. 5 | Best served chilled with [irb-config](https://github.com/nviennot/irb-config). 6 | 7 | It is especially helpful when working with [jRuby](http://jruby.org/), because it will keep an active JVM running for you. This drastically reduces the feedback loop of doing TDD in jRuby -- and all without messing with nail-gun! 8 | 9 | ### Watch the screencast 10 | 11 | [![Watch the screencast!](https://s3.amazonaws.com/velvetpulse/screencasts/irb-config-screencast.jpg)](http://velvetpulse.com/2012/11/19/improve-your-ruby-workflow-by-integrating-vim-tmux-pry) 12 | 13 | Usage 14 | ------ 15 | 16 | ### 1) Install rspec-console with: 17 | 18 | ```ruby 19 | gem 'rspec-console' 20 | ``` 21 | 22 | ### 2) With Rails, disable cache\_classes so reload! function properly 23 | 24 | Ensure you turned off Rails's `cache_classes` in the config/environment/test.rb file: 25 | 26 | ```ruby 27 | Rails.application.configure do 28 | # turn off this! 29 | config.cache_classes = false 30 | end 31 | ``` 32 | 33 | ### 3) Launch your console 34 | 35 | With Rails, launch your console with `rails c test`. 36 | 37 | ### 4) Launch your tests 38 | 39 | If you have [Pry](https://github.com/pry/pry) installed, you will have access to the `rspec` command 40 | in your console, which works exactly like the shell command line rspec one. 41 | 42 | If you don't have pry, you can use: 43 | 44 | ```ruby 45 | RSpecConsole.run 'spec/integration/closing_brand_action_spec.rb:33' '--format=doc' 46 | ``` 47 | 48 | Example 49 | ------- 50 | 51 | ``` 52 | pafy@bisou ~/prj/sniper [master●] % rails c test 53 | ~/prj/crowdtap/sniper (test) > rspec spec/integration/closing_brand_action_spec.rb:33 --format=doc 54 | Run options: include {:locations=>{"./spec/integration/closing_brand_action_spec.rb"=>[33]}} 55 | 56 | Sniper 57 | when reaching the maximum number of participants 58 | no longer targets this brand action on members 59 | 60 | Finished in 0.12654 seconds 61 | 1 example, 0 failures 62 | ~/prj/crowdtap/sniper (test) > 63 | ``` 64 | 65 | Authors 66 | ------- 67 | 68 | * [Alex Moore-Niemi](https://github.com/mooreniemi) 69 | * Nicolas Viennot 70 | 71 | 72 | License 73 | ------- 74 | 75 | MIT License 76 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | 4 | task default: :test 5 | Rake::TestTask.new do |t| 6 | t.libs << "test" 7 | t.pattern = "test/**/*_test.rb" 8 | end 9 | -------------------------------------------------------------------------------- /gemfiles/rspec_2_10_0.Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec path: '..' 3 | 4 | gem 'rspec', '2.10.0' 5 | -------------------------------------------------------------------------------- /gemfiles/rspec_2_14_1.Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec path: '..' 3 | 4 | gem 'rspec', '2.14.1' 5 | -------------------------------------------------------------------------------- /gemfiles/rspec_3_0_0.Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec path: '..' 3 | 4 | gem 'rspec', '3.0.0' 5 | -------------------------------------------------------------------------------- /gemfiles/rspec_latest.Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec path: '..' 3 | 4 | gem 'rspec' 5 | -------------------------------------------------------------------------------- /lib/rspec-console.rb: -------------------------------------------------------------------------------- 1 | module RSpecConsole 2 | autoload :ConfigCache, 'rspec-console/config_cache' 3 | autoload :RSpecState, 'rspec-console/rspec_state' 4 | autoload :Runner, 'rspec-console/runner' 5 | autoload :Pry, 'rspec-console/pry' 6 | 7 | class << self; attr_accessor :before_run_callbacks; end 8 | self.before_run_callbacks = [] 9 | 10 | def self.run(*args) 11 | Runner.run(args) 12 | end 13 | 14 | def self.before_run(&hook) 15 | self.before_run_callbacks << hook 16 | end 17 | 18 | Pry.setup if defined?(::Pry) 19 | 20 | # We only want the test env 21 | before_run do 22 | if defined?(Rails) && !(Rails.env =~ /test/) 23 | raise 'Please run in test mode (run `rails console test`).' 24 | end 25 | end 26 | 27 | # Emit warning when reload cannot be called, or call reload! 28 | before_run do 29 | class String 30 | def red 31 | "\033[31m#{self}\033[0m" 32 | end 33 | end 34 | if defined?(Rails) 35 | if Rails.application.config.cache_classes 36 | STDERR.puts <<-MSG.gsub(/^ {10}/, '') 37 | #{"[ WARNING ]".red } 38 | Rails's cache_classes must be turned off. 39 | Turn it off in config/environments/test.rb: 40 | 41 | Rails.application.configure do 42 | config.cache_classes = false 43 | end 44 | 45 | Otherwise, code relading does not work. 46 | MSG 47 | else 48 | ActionDispatch::Reloader.cleanup! 49 | ActionDispatch::Reloader.prepare! 50 | end 51 | end 52 | end 53 | 54 | # Reloading FactoryGirl if necessary 55 | before_run { FactoryGirl.reload if defined?(FactoryGirl) } 56 | end 57 | -------------------------------------------------------------------------------- /lib/rspec-console/config_cache.rb: -------------------------------------------------------------------------------- 1 | class RSpecConsole::ConfigCache 2 | # We have to reset the RSpec.configuration, because it contains a lot of 3 | # information related to the current test (what's running, what are the 4 | # different test results, etc). 5 | # 6 | # RSpec.configuration gets also loaded with a bunch of stuff from the 7 | # 'spec/spec_helper.rb' file. Often that instance is extended with other 8 | # modules (FactoryGirl, Mocha,...) and we don't want to replace requires with 9 | # load all around the place. 10 | # 11 | # Instead, we proxy and record whatever is done to RSpec.configuration during 12 | # the first invocation of require('spec_helper'). This is done by interposing 13 | # the RecordingProxy class on of RSpec.configuration. 14 | attr_accessor :config_proxy, :root_shared_examples 15 | 16 | class RecordingProxy < Struct.new(:target, :recorded_messages) 17 | [:include, :extend].each do |method| 18 | define_method(method) do |*args| 19 | method_missing(method, *args) 20 | end 21 | end 22 | 23 | def method_missing(method, *args, &block) 24 | self.recorded_messages << [method, args, block] 25 | self.target.send(method, *args, &block) 26 | end 27 | end 28 | 29 | def record_configuration(&configuration_block) 30 | ensure_configuration_setter! 31 | 32 | original_config = ::RSpec.configuration 33 | ::RSpec.configuration = RecordingProxy.new(original_config, []) 34 | 35 | configuration_block.call # spec helper is called during this yield, see #reset 36 | 37 | self.config_proxy = ::RSpec.configuration 38 | ::RSpec.configuration = original_config 39 | 40 | stash_shared_examples 41 | 42 | forward_rspec_config_singleton_to(self.config_proxy) 43 | end 44 | 45 | def replay_configuration 46 | ::RSpec.configure do |config| 47 | self.config_proxy.recorded_messages.each do |method, args, block| 48 | # reporter caches config.output_stream which is not good as it 49 | # prevents the runner to use a custom stdout. 50 | next if method == :reporter 51 | config.send(method, *args, &block) 52 | end 53 | end 54 | 55 | restore_shared_examples 56 | 57 | forward_rspec_config_singleton_to(self.config_proxy) 58 | end 59 | 60 | def has_recorded_config? 61 | !!self.config_proxy 62 | end 63 | 64 | def forward_rspec_config_singleton_to(config_proxy) 65 | # an old version of rspec-rails/lib/rspec/rails/view_rendering.rb adds 66 | # methods on the configuration singleton. This takes care of that. 67 | ::RSpec.configuration.singleton_class 68 | .send(:define_method, :method_missing, &config_proxy.method(:send)) 69 | end 70 | 71 | def stash_shared_examples 72 | self.root_shared_examples = case shared_example_api 73 | when :v1 then ::RSpec.world.shared_example_groups.dup 74 | when :v2 then ::RSpec.world.shared_example_group_registry.send(:shared_example_groups).dup 75 | end 76 | end 77 | 78 | def restore_shared_examples 79 | case shared_example_api 80 | when :v1 then ::RSpec.world.shared_example_groups.merge!(self.root_shared_examples) 81 | when :v2 82 | self.root_shared_examples.each do |context, name_blocks| 83 | name_blocks.each do |name, block| 84 | ::RSpec.world.shared_example_group_registry.add(context, name, &block) 85 | end 86 | end 87 | end 88 | end 89 | 90 | def shared_example_api 91 | return :v1 if ::RSpec.world.respond_to?(:shared_example_groups) 92 | return :v2 if ::RSpec.world.respond_to?(:shared_example_group_registry) 93 | end 94 | 95 | def ensure_configuration_setter! 96 | return if RSpec.respond_to?(:configuration=) 97 | 98 | ::RSpec.instance_eval do 99 | def self.configuration=(value) 100 | @configuration = value 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/rspec-console/pry.rb: -------------------------------------------------------------------------------- 1 | module RSpecConsole 2 | module Pry 3 | def self.setup 4 | ::Pry::CommandSet.new(&method(:rspec_command)) 5 | .tap { |cmd| ::Pry::Commands.import cmd } 6 | end 7 | 8 | def self.rspec_command(cmd) 9 | cmd.create_command "rspec", "Runs specs; to silence ActiveRecord output use SILENCE_AR=true" do 10 | group "Testing" 11 | 12 | def process(*args) 13 | with_ar_silenced { RSpecConsole::Runner.run(args) } 14 | end 15 | 16 | def with_ar_silenced(&block) 17 | return block.call unless defined?(ActiveRecord) && ENV['SILENCE_AR'] 18 | begin 19 | old_logger, ActiveRecord::Base.logger = ActiveRecord::Base.logger, nil 20 | block.call 21 | ensure 22 | ActiveRecord::Base.logger = old_logger 23 | end 24 | end 25 | 26 | def complete(input) 27 | require 'bond' 28 | super + Bond::Rc.files(input.split(" ").last || '') 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rspec-console/rspec_state.rb: -------------------------------------------------------------------------------- 1 | module RSpecConsole 2 | class RSpecState 3 | class << self 4 | def reset 5 | require 'rspec/core' 6 | raise 'Please use RSpec 2.10.0 or later' if obsolete_rspec? 7 | 8 | ::RSpec::Core::Configuration.class_eval { define_method(:command) { 'rspec' } } 9 | ::RSpec::Core::Runner.disable_autorun! 10 | ::RSpec.reset 11 | 12 | if config_cache.has_recorded_config? 13 | config_cache.replay_configuration 14 | else 15 | config_cache.record_configuration(&rspec_configuration) 16 | end 17 | end 18 | 19 | def rspec_configuration 20 | proc do 21 | ::RSpec.configure do |config| 22 | config.color_enabled = true if config.respond_to?(:color_enabled=) 23 | config.color = true if config.respond_to?(:color=) 24 | end 25 | 26 | $LOAD_PATH << './spec' 27 | try_load('spec_helper') 28 | try_load('rails_helper') 29 | end 30 | end 31 | 32 | def try_load(file) 33 | begin 34 | require file 35 | rescue LoadError 36 | end 37 | end 38 | 39 | def config_cache 40 | @config_cache ||= RSpecConsole::ConfigCache.new 41 | end 42 | 43 | def obsolete_rspec? 44 | Gem.loaded_specs['rspec-core'].version < Gem::Version.new('2.10.0') 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/rspec-console/runner.rb: -------------------------------------------------------------------------------- 1 | # This class wraps the core rspec runner and manages the environment around it. 2 | module RSpecConsole 3 | class Runner 4 | class << self 5 | def run(args, options={}) 6 | RSpecConsole.before_run_callbacks.each(&:call) 7 | RSpecConsole::RSpecState.reset 8 | 9 | stdout = options[:stdout] || $stdout 10 | stderr = options[:stderr] || $stderr 11 | 12 | ::RSpec::Core::Runner.run(args, stderr, stdout) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /rspec-console.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | $:.unshift File.expand_path("../lib", __FILE__) 3 | $:.unshift File.expand_path("../../lib", __FILE__) 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "rspec-console" 7 | s.version = '0.6.1' 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Nicolas Viennot"] 10 | s.email = ["nicolas@viennot.biz"] 11 | s.homepage = "http://github.com/nviennot/rspec-console" 12 | s.summary = "Run RSpec tests in your console" 13 | s.description = "Run RSpec tests in your console" 14 | s.license = "MIT" 15 | 16 | s.add_dependency 'bond' 17 | s.add_development_dependency 'rake' 18 | s.add_development_dependency 'pry' 19 | s.add_development_dependency 'minitest' 20 | 21 | s.files = Dir["lib/**/*"] + ['README.md'] 22 | s.require_path = 'lib' 23 | s.has_rdoc = false 24 | end 25 | -------------------------------------------------------------------------------- /spec/fail_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'fail' do 4 | it 'fail 1' do 5 | $rspec.fail1 6 | raise 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'helpers' do 4 | it 'works' do 5 | if $rspec2 6 | helper_hello_world.should == 'ohai :)' 7 | else 8 | expect(helper_hello_world).to eq('ohai :)') 9 | end 10 | end 11 | end 12 | 13 | describe 'singleton methods' do 14 | it 'works' do 15 | if $rspec2 16 | RSpec.configuration.some_config_method.should == 'yay' 17 | else 18 | expect(RSpec.configuration.some_config_method).to eq('yay') 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/shared_examples_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'some shared examples 1' do 4 | shared_examples_for 'included shared example' do 5 | it 'works 1' do 6 | $rspec.shared_examples11 7 | end 8 | 9 | it 'works 2' do 10 | $rspec.shared_examples12 11 | end 12 | end 13 | 14 | context 'one' do 15 | it_behaves_like 'included shared example' 16 | end 17 | 18 | context 'two' do 19 | it_behaves_like 'included shared example' 20 | end 21 | 22 | context 'root' do 23 | it_behaves_like 'root shared examples' 24 | end 25 | end 26 | 27 | -------------------------------------------------------------------------------- /spec/simple_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'simple' do 4 | it 'test 1' do 5 | $rspec.test1 6 | end 7 | 8 | it 'test 2' do 9 | $rspec.test2 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This is a fixture for the tests 2 | # This is *not* the actual tests for rspec-console (see in ./test) 3 | 4 | unless $running_with_minitest 5 | STDERR.puts "These are the fixtures for the test written with minitest" 6 | STDERR.puts "Run `rake` to run the test suite" 7 | exit 8 | end 9 | 10 | RSpec.configure do |config| 11 | config.before(:all) { $rspec.config_before_all } 12 | end 13 | 14 | module SomeHelper 15 | def helper_hello_world 16 | 'ohai :)' 17 | end 18 | 19 | RSpec.configure { |config| config.include self } 20 | end 21 | 22 | RSpec.configure do |config| 23 | def config.some_config_method 24 | 'yay' 25 | end 26 | end 27 | 28 | RSpec.shared_examples 'root shared examples' do 29 | it 'works 1' do 30 | $rspec.root_shared_examples1 31 | end 32 | 33 | it 'works 2' do 34 | $rspec.root_shared_examples2 35 | end 36 | end 37 | 38 | $rspec2 = Gem.loaded_specs['rspec-core'].version < Gem::Version.new('3') 39 | -------------------------------------------------------------------------------- /test/integration/rspec_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BasicTest < Minitest::Test 4 | def test_simple 5 | rspec_run('spec/simple_spec.rb') 6 | assert_rspec_methods [:config_before_all, :test1, :test2] 7 | assert_rspec_output :examples => 2 8 | end 9 | 10 | def test_multiple 11 | rspec_run('spec/simple_spec.rb:4') 12 | assert_rspec_methods [:config_before_all, :test1] 13 | assert_rspec_output :examples => 1 14 | 15 | rspec_run('spec/simple_spec.rb:8') 16 | assert_rspec_methods [:config_before_all, :test2] 17 | assert_rspec_output :examples => 1 18 | 19 | rspec_run('spec/simple_spec.rb:4', 'spec/simple_spec.rb:8') 20 | assert_rspec_methods [:config_before_all, :test1, :test2] 21 | assert_rspec_output :examples => 2 22 | end 23 | 24 | def test_helper 25 | rspec_run('spec/helper_spec.rb:3') 26 | assert_rspec_output :examples => 1 27 | end 28 | 29 | def test_singleton 30 | rspec_run('spec/helper_spec.rb:9') 31 | assert_rspec_output :examples => 1 32 | end 33 | 34 | def test_shared_examples 35 | rspec_run('spec/shared_examples_spec.rb') 36 | assert_rspec_output :examples => 6 37 | end 38 | 39 | def test_fail 40 | rspec_run('spec/fail_spec.rb') 41 | assert_rspec_methods [:config_before_all, :fail1] 42 | assert_rspec_output :examples => 1, :failures => 1 43 | end 44 | 45 | def test_all 46 | rspec_run() 47 | assert_rspec_output :examples => 11, :failures => 1 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | require 'minitest/autorun' 5 | require 'minitest/pride' 6 | require 'minitest/spec' 7 | 8 | puts "Testing with rspec version #{Gem.loaded_specs['rspec-core'].version}" 9 | $running_with_minitest = true 10 | 11 | class Minitest::Test 12 | class CatchAll < Struct.new(:messages) 13 | def initialize 14 | self.messages = [] 15 | end 16 | 17 | def method_missing(method, *args, &block) 18 | messages << [method.to_sym, args, block] 19 | end 20 | 21 | def methods_called 22 | messages.map { |method, args, block| method } 23 | end 24 | end 25 | 26 | def rspec_run(*args) 27 | stdout = StringIO.new 28 | @rspec = $rspec = CatchAll.new 29 | RSpecConsole::Runner.run(args, :stdout => stdout, :stderr => stdout) 30 | ensure 31 | @stdout = stdout.tap { |s| s.rewind }.read 32 | end 33 | 34 | def assert_rspec_output(options={}) 35 | options[:failures] ||= 0 36 | assert_match(/Finished.*#{options[:examples]} example.*#{options[:failures]} failure/m, @stdout) 37 | end 38 | 39 | 40 | def assert_rspec_methods(methods) 41 | assert_equal(methods, @rspec.methods_called) 42 | end 43 | end 44 | --------------------------------------------------------------------------------