diff --git a/.travis.yml b/.travis.yml index 43134b4054..fc8b529320 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,14 @@ script: "bin/rake --trace 2>&1" bundler_args: "--binstubs --without documentation" -before_install: gem install bundler --pre +before_install: gem install bundler rvm: - 1.8.7 - 1.9.2 - 1.9.3 - 2.0.0 env: - - RAILS_VERSION=3.2.12 - - RAILS_VERSION=3.1.11 + - RAILS_VERSION=3.2.13 + - RAILS_VERSION=3.1.12 - RAILS_VERSION=3.0.20 - RAILS_VERSION=3-2-stable - RAILS_VERSION=3-1-stable @@ -16,24 +16,18 @@ env: - RAILS_VERSION=master matrix: exclude: - - rvm: 1.8.7 - env: RAILS_VERSION=master - - rvm: 1.9.2 - env: RAILS_VERSION=master - - rvm: 2.0.0 - env: RAILS_VERSION=3.1.11 + # 3.0.x is not supported on MRI 2.0.0 - rvm: 2.0.0 env: RAILS_VERSION=3.0.20 - - rvm: 2.0.0 - env: RAILS_VERSION=3-2-stable - - rvm: 2.0.0 - env: RAILS_VERSION=3-1-stable - rvm: 2.0.0 env: RAILS_VERSION=3-0-stable - allow_failures: - - rvm: 1.9.3 + # 4.0.x is not supported on MRI 1.8.7 or 1.9.2 + - rvm: 1.8.7 + env: RAILS_VERSION=master + - rvm: 1.9.2 env: RAILS_VERSION=master + allow_failures: + - env: RAILS_VERSION=master - env: RAILS_VERSION=3-2-stable - env: RAILS_VERSION=3-1-stable - env: RAILS_VERSION=3-0-stable - - rvm: 2.0.0 diff --git a/Capybara.md b/Capybara.md index 57342b3b68..33f327aac0 100644 --- a/Capybara.md +++ b/Capybara.md @@ -3,8 +3,6 @@ its Capybara::DSL (visit/page) and Capybara::RSpecMatchers to the examples in the applicable directories, which differ slightly between Capybara 1.x and Capybara >= 2.x. -Note that you need to require "capybara/rspec" for this integration to work. - ## Capybara::DSL Adds the `visit` and `page` methods, which work together to simulate a @@ -12,7 +10,7 @@ GET request and provide access to the result (via `page`). ## Capybara::RSpecMatchers -Exposes matchers used to specify expected HTML content (e.g. `have_selector`). +Exposes matchers used to specify expected HTML content (e.g. `should_not have_selector` will work correctly). ## Capybara 1.x diff --git a/Changelog.md b/Changelog.md index bfa69abb08..96a3204182 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,17 @@ +### 2.13.1 +[full changelog](http://github.com/rspec/rspec-rails/compare/v2.13.0...v2.13.1) + +Bug fixes + +* View specs are no longer generated if no template engine is specified (Kevin + Glowacz) +* `ActionController::Base.allow_forgery_protection` is set to its original + value after each example. (Mark Dimas) +* `patch` is supported in routing specs. (Chris Your) +* Routing assertions are supported in controller specs in Rails 4. (Andy + Lindeman) +* Fix spacing in the install generator template (Taiki ONO) + ### 2.13.0 / 2013-02-23 [full changelog](http://github.com/rspec/rspec-rails/compare/v2.12.2...v2.13.0) diff --git a/Gemfile b/Gemfile index 0b89071aaa..335bd04147 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org/" +source "https://rubygems.org/" gemspec @@ -26,6 +26,11 @@ end gem 'sqlite3', '~> 1.3.6' +# Capybara 2.1 requires Ruby >= 1.9.3 +if RUBY_VERSION < '1.9.3' + gem 'capybara', '>= 2.0.0', '< 2.1.0' +end + custom_gemfile = File.expand_path("../Gemfile-custom", __FILE__) eval File.read(custom_gemfile) if File.exist?(custom_gemfile) @@ -47,9 +52,7 @@ when /3-1-stable/ when /3-2-stable/ gem "rails", :git => "git://github.com/rails/rails.git", :branch => "3-2-stable" when nil, false, "" - gem "rails", "3.2.12" + gem "rails", "3.2.13" else gem "rails", version end - -gem 'relish' diff --git a/README.md b/README.md index 8383082c80..7b43e6d749 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,58 @@ # rspec-rails [![Build Status](https://secure.travis-ci.org/rspec/rspec-rails.png?branch=master)](http://travis-ci.org/rspec/rspec-rails) [![Code Climate](https://codeclimate.com/github/rspec/rspec-rails.png)](https://codeclimate.com/github/rspec/rspec-rails) -**rspec-rails 2** is a testing framework for Rails 3.x and 4.x. +**rspec-rails** is a testing framework for Rails 3.x and 4.x. -Use **[rspec-rails 1](http://github.com/dchelimsky/rspec-rails)** for Rails 2.x. +Use **[rspec-rails 1.x](http://github.com/dchelimsky/rspec-rails)** for Rails +2.x. -## Install +## Installation -Add `rspec-rails` to the `:test` and `:development` groups in the Gemfile: +Add `rspec-rails` to **both** the `:development` and `:test` groups in the +`Gemfile`: ```ruby -group :test, :development do - gem "rspec-rails", "~> 2.0" +group :development, :test do + gem 'rspec-rails', '~> 2.0' end ``` -It needs to be in the `:development` group to expose generators and rake -tasks without having to type `RAILS_ENV=test`. +Download and install by running: -Now you can run: +``` +bundle install +``` + +Initialize the `spec/` directory (where specs will reside) with: ``` rails generate rspec:install ``` -This adds the spec directory and some skeleton files, including -the "rake spec" task. +To run your specs, use the `rspec` command: + +``` +bundle exec rspec + +# Run only model specs +bundle exec rspec spec/models + +# Run only specs for AccountsController +bundle exec rspec spec/controllers/accounts_controller_spec.rb +``` + +Specs can also be run via `rake spec`, though this command may be slower to +start than the `rspec` command. + +In Rails 4, you may want to create a binstub for the `rspec` command so it can +be run via `bin/rspec`: + +``` +bundle binstubs rspec-core +``` ### Generators -Once installed, RSpec will generate spec file instead of Test::Unit test files +Once installed, RSpec will generate spec files instead of Test::Unit test files when commands like `rails generate model` and `rails generate controller` are used. @@ -67,7 +91,7 @@ describe PostsController do it "responds successfully with an HTTP 200 status code" do get :index expect(response).to be_success - expect(response.code).to eq(200) + expect(response.status).to eq(200) end it "renders the index template" do @@ -97,7 +121,8 @@ spec](#feature-specs). ## Request Specs -Request specs live in spec/requests, spec/api and spec/integration, and mix in behavior +Request specs live in spec/requests, spec/features, spec/api and +spec/integration, and mix in behavior [ActionDispatch::Integration::Runner](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html), which is the basis for [Rails' integration tests](http://guides.rubyonrails.org/testing.html#integration-testing). The @@ -123,7 +148,9 @@ end ``` This example uses only standard Rails and RSpec API's, but many RSpec/Rails -users like to use extension libraries like [FactoryGirl](https://github.com/thoughtbot/factory_girl) and [Capybara](https://github.com/jnicklas/capybara): +users like to use extension libraries like +[FactoryGirl](https://github.com/thoughtbot/factory_girl) and +[Capybara](https://github.com/jnicklas/capybara): ```ruby require 'spec_helper' @@ -145,7 +172,10 @@ which can be encoded into the underlying factory definition without requiring changes to this example. Among other benefits, Capybara binds the form post to the generated HTML, which -means we don't need to specify them separately. +means we don't need to specify them separately. Note that Capybara's DSL as +shown is, by default, only available in specs in the spec/features directory. +For more information, see the [Capybara integration +docs](http://rubydoc.info/gems/rspec-rails/file/Capybara.md). There are several other Ruby libs that implement the factory pattern or provide a DSL for request specs (a.k.a. acceptance or integration specs), but diff --git a/Rakefile b/Rakefile index 0c337f93ac..7b0aae3a46 100644 --- a/Rakefile +++ b/Rakefile @@ -48,8 +48,12 @@ namespace :generate do desc "generate a fresh app with rspec installed" task :app do |t| unless File.directory?('./tmp/example_app') - sh "rails new ./tmp/example_app --skip-javascript --skip-gemfile --skip-git --skip-test-unit" bindir = File.expand_path("bin") + + # Rails 4 cannot use a `rails` binstub generated by Bundler + sh "rm -f #{bindir}/rails" + sh "bundle exec rails new ./tmp/example_app --skip-javascript --skip-gemfile --skip-git --skip-test-unit" + if test ?d, bindir Dir.chdir("./tmp/example_app") do Dir.mkdir("bin") unless File.directory?("bin") diff --git a/features/.nav b/features/.nav index 254ecb083a..eedf6de953 100644 --- a/features/.nav +++ b/features/.nav @@ -17,6 +17,7 @@ - render_views.feature - anonymous_controller.feature - bypass_rescue.feature + - engine_routes.feature - matchers: - new_record_matcher.feature - render_template_matcher.feature @@ -37,3 +38,4 @@ - route_to_matcher.feature - be_routable_matcher.feature - named_routes.feature + - engine_routes.feature diff --git a/features/Transactions.md b/features/Transactions.md index 39604b97dc..400e9c3ab1 100644 --- a/features/Transactions.md +++ b/features/Transactions.md @@ -36,11 +36,11 @@ already ran. For example: end it "does something" do - @widget.should do_something + expect(@widget).to do_something end it "does something else" do - @widget.should do_something_else + expect(@widget).to do_something_else end end diff --git a/features/Upgrade.md b/features/Upgrade.md index 5710748038..c1e3b9e95f 100644 --- a/features/Upgrade.md +++ b/features/Upgrade.md @@ -60,6 +60,10 @@ This needs to move from before the action to after. For example: get :edit, :id => "37" response.should render_template("edit") + # rspec-rails-2 with expect syntax + get :edit, :id => "37" + expect(response).to render_template("edit") + rspec-1 had to monkey patch Rails to get render_template to work before the action, and this broke a couple of times with Rails releases (requiring urgent fix releases in RSpec). Part of the philosophy of rspec-rails-2 is to rely on diff --git a/features/controller_specs/Cookies.md b/features/controller_specs/Cookies.md index ceca3e142b..23c1e9be59 100644 --- a/features/controller_specs/Cookies.md +++ b/features/controller_specs/Cookies.md @@ -20,7 +20,7 @@ the following guidelines: # spec request.cookies['foo'] = 'bar' get :some_action -response.cookies['foo'].should eq('modified bar') +expect(response.cookies['foo']).to eq('modified bar') # controller def some_action diff --git a/features/controller_specs/README.md b/features/controller_specs/README.md index 01e0422ff1..d29c68695a 100644 --- a/features/controller_specs/README.md +++ b/features/controller_specs/README.md @@ -12,15 +12,15 @@ specify expected outcomes such as: * cookies sent back with the response To specify outcomes, you can use: - -* standard rspec matchers (`response.code.should eq(200)`) -* standard test/unit assertions (`assert_equal 200, response.code`) + +* standard rspec matchers (`expect(response.status).to eq(200)`) +* standard test/unit assertions (`assert_equal 200, response.status`) * rails assertions (`assert_response 200`) * rails-specific matchers: - * `response.should render_template (wraps assert_template)` - * `response.should redirect_to (wraps assert_redirected_to)` - * `assigns(:widget).should be_a_new(Widget)` - + * `expect(response).to render_template(wraps assert_template)` + * `expect(response).to redirect_to(wraps assert_redirected_to)` + * `expect(assigns(:widget)).to be_a_new(Widget)` + ## Examples describe TeamsController do @@ -28,12 +28,12 @@ To specify outcomes, you can use: it "assigns @teams" do team = Team.create get :index - assigns(:teams).should eq([team]) + expect(assigns(:teams)).to eq([team]) end it "renders the index template" do get :index - response.should render_template("index") + expect(response).to render_template("index") end end end diff --git a/features/controller_specs/controller_spec.feature b/features/controller_specs/controller_spec.feature index 53c5dddf96..2df0320201 100644 --- a/features/controller_specs/controller_spec.feature +++ b/features/controller_specs/controller_spec.feature @@ -9,7 +9,7 @@ Feature: controller spec describe "GET index" do it "has a 200 status code" do get :index - expect(response.code).to eq("200") + expect(response.status).to eq(200) end end end diff --git a/features/controller_specs/engine_routes.feature b/features/controller_specs/engine_routes.feature new file mode 100644 index 0000000000..1c4ef8a217 --- /dev/null +++ b/features/controller_specs/engine_routes.feature @@ -0,0 +1,51 @@ +Feature: engine routes for controllers + + Controller specs can specify the routeset that will be used for the example + group. This is most useful when testing Rails engines. + + @unsupported-on-rails-3-0 + Scenario: specify engine route + Given a file named "spec/controllers/widgets_controller_spec.rb" with: + """ruby + require "spec_helper" + + # A very simple Rails engine + module MyEngine + class Engine < ::Rails::Engine + isolate_namespace MyEngine + end + + Engine.routes.draw do + resources :widgets, :only => [:show] do + get :random, :on => :collection + end + end + + class WidgetsController < ::ActionController::Base + def random + @random_widget = Widget.all.shuffle.first + redirect_to widget_path(@random_widget) + end + + def show + @widget = Widget.find(params[:id]) + render :text => @widget.name + end + end + end + + describe MyEngine::WidgetsController do + routes { MyEngine::Engine.routes } + + it "redirects to a random widget" do + widget1 = Widget.create!(:name => "Widget 1") + widget2 = Widget.create!(:name => "Widget 2") + + get :random + expect(response).to be_redirect + expect(response).to redirect_to(assigns(:random_widget)) + end + end + """ + When I run `rspec spec` + Then the examples should all pass diff --git a/features/helper_specs/helper_spec.feature b/features/helper_specs/helper_spec.feature index 8c364f8aba..ada3316569 100644 --- a/features/helper_specs/helper_spec.feature +++ b/features/helper_specs/helper_spec.feature @@ -94,3 +94,29 @@ Feature: helper spec """ When I run `rspec spec/helpers/widgets_helper_spec.rb` Then the examples should all pass + + Scenario: url helpers are defined + Given a file named "spec/helpers/widgets_helper_spec.rb" with: + """ruby + require "spec_helper" + + describe WidgetsHelper do + describe "#link_to_widget" do + it "links to a widget using its name" do + widget = Widget.create!(:name => "This Widget") + expect(helper.link_to_widget(widget)).to include("This Widget") + expect(helper.link_to_widget(widget)).to include(widget_path(widget)) + end + end + end + """ + And a file named "app/helpers/widgets_helper.rb" with: + """ruby + module WidgetsHelper + def link_to_widget(widget) + link_to(widget.name, widget_path(widget)) + end + end + """ + When I run `rspec spec/helpers/widgets_helper_spec.rb` + Then the examples should all pass diff --git a/features/matchers/README.md b/features/matchers/README.md index a591027abd..76efa29316 100644 --- a/features/matchers/README.md +++ b/features/matchers/README.md @@ -4,15 +4,15 @@ rspec-compatible wrappers for Rails' assertions. ### redirects # delegates to assert_redirected_to - response.should redirect_to(path) + expect(response).to redirect_to(path) ### templates # delegates to assert_template - response.should render_template(template_name) + expect(response).to render_template(template_name) ### assigned objects # passes if assigns(:widget) is an instance of Widget # and it is not persisted - assigns(:widget).should be_a_new(Widget) + expect(assigns(:widget)).to be_a_new(Widget) diff --git a/features/model_specs/errors_on.feature b/features/model_specs/errors_on.feature index 77c1b0b9a5..375f72e305 100644 --- a/features/model_specs/errors_on.feature +++ b/features/model_specs/errors_on.feature @@ -10,7 +10,7 @@ Feature: errors_on validates_presence_of :name # In Rails 4, mass assignment protection is implemented on controllers - attr_accessible :name if Rails.version < '4' + attr_accessible :name if RSpec::Rails.rails_version_satisfied_by?('< 4.0.0.beta1') validates_length_of :name, :minimum => 10, :on => :publication end diff --git a/features/routing_specs/README.md b/features/routing_specs/README.md index 0473c3df56..607a9e7c96 100644 --- a/features/routing_specs/README.md +++ b/features/routing_specs/README.md @@ -5,13 +5,12 @@ Simple apps with nothing but standard RESTful routes won't get much value from routing specs, but they can provide significant value when used to specify customized routes, like vanity links, slugs, etc. - { :get => "/articles/2012/11/when-to-use-routing-specs" }. - should route_to( - :controller => "articles", - :month => "2012-11", - :slug => "when-to-use-routing-specs" - ) + expect(:get => "/articles/2012/11/when-to-use-routing-specs").to route_to( + :controller => "articles", + :month => "2012-11", + :slug => "when-to-use-routing-specs" + ) They are also valuable for routes that should not be available: - { :delete => "/accounts/37" }.should_not be_routable \ No newline at end of file + expect(:delete => "/accounts/37").not_to be_routable diff --git a/features/routing_specs/engine_routes.feature b/features/routing_specs/engine_routes.feature new file mode 100644 index 0000000000..e7d412fce8 --- /dev/null +++ b/features/routing_specs/engine_routes.feature @@ -0,0 +1,38 @@ +Feature: engine routes + + Routing specs can specify the routeset that will be used for the example + group. This is most useful when testing Rails engines. + + @unsupported-on-rails-3-0 + Scenario: specify engine route + Given a file named "spec/routing/engine_routes_spec.rb" with: + """ruby + require "spec_helper" + + # A very simple Rails engine + module MyEngine + class Engine < ::Rails::Engine + isolate_namespace MyEngine + end + + Engine.routes.draw do + resources :widgets, :only => [:index] + end + + class WidgetsController < ::ActionController::Base + def index + end + end + end + + describe MyEngine::WidgetsController do + routes { MyEngine::Engine.routes } + + it "routes to the list of all widgets" do + expect(:get => widgets_path). + to route_to(:controller => "my_engine/widgets", :action => "index") + end + end + """ + When I run `rspec spec` + Then the examples should all pass diff --git a/features/support/rails_versions.rb b/features/support/rails_versions.rb new file mode 100644 index 0000000000..c2fb48cf0f --- /dev/null +++ b/features/support/rails_versions.rb @@ -0,0 +1,4 @@ +Around "@unsupported-on-rails-3-0" do |scenario, block| + require 'rails' + block.call unless ::Rails.version.to_s.start_with?("3.0") +end diff --git a/lib/generators/rspec.rb b/lib/generators/rspec.rb index 66e2f03fda..b1aad6aafe 100644 --- a/lib/generators/rspec.rb +++ b/lib/generators/rspec.rb @@ -1,4 +1,5 @@ require 'rails/generators/named_base' +require 'rspec/rails/rails_version' module Rspec module Generators @@ -7,7 +8,7 @@ def self.source_root @_rspec_source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'rspec', generator_name, 'templates')) end - if ::Rails.version < '3.1' + if RSpec::Rails.rails_version_satisfied_by?('< 3.1') def module_namespacing yield if block_given? end diff --git a/lib/generators/rspec/controller/controller_generator.rb b/lib/generators/rspec/controller/controller_generator.rb index 6a43f1881e..0625c97eb5 100644 --- a/lib/generators/rspec/controller/controller_generator.rb +++ b/lib/generators/rspec/controller/controller_generator.rb @@ -18,7 +18,7 @@ def generate_controller_spec def generate_view_specs return if actions.empty? - return unless options[:view_specs] + return unless options[:view_specs] && options[:template_engine] empty_directory File.join("spec", "views", file_path) diff --git a/lib/generators/rspec/install/install_generator.rb b/lib/generators/rspec/install/install_generator.rb index cb09c490be..f683fd0803 100644 --- a/lib/generators/rspec/install/install_generator.rb +++ b/lib/generators/rspec/install/install_generator.rb @@ -1,3 +1,5 @@ +require 'rspec/rails/rails_version' + module Rspec module Generators class InstallGenerator < ::Rails::Generators::Base diff --git a/lib/generators/rspec/install/templates/spec/spec_helper.rb.tt b/lib/generators/rspec/install/templates/spec/spec_helper.rb.tt index 635cef1629..1b4c1c87fc 100644 --- a/lib/generators/rspec/install/templates/spec/spec_helper.rb.tt +++ b/lib/generators/rspec/install/templates/spec/spec_helper.rb.tt @@ -6,9 +6,9 @@ require 'rspec/autorun' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. -Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} +Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } -<% if ::Rails.version >= '4' -%> +<% if RSpec::Rails.rails_version_satisfied_by?('>= 4.0.0.beta1') -%> # Checks for pending migrations before tests are run. # If you are not using ActiveRecord, you can remove this line. ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) diff --git a/lib/generators/rspec/scaffold/scaffold_generator.rb b/lib/generators/rspec/scaffold/scaffold_generator.rb index 43ef3eac6c..8c36d3fe5c 100644 --- a/lib/generators/rspec/scaffold/scaffold_generator.rb +++ b/lib/generators/rspec/scaffold/scaffold_generator.rb @@ -27,7 +27,7 @@ def generate_controller_spec end def generate_view_specs - return unless options[:view_specs] + return unless options[:view_specs] && options[:template_engine] copy_view :edit copy_view :index unless options[:singleton] diff --git a/lib/generators/rspec/scaffold/templates/controller_spec.rb b/lib/generators/rspec/scaffold/templates/controller_spec.rb index 95f1cd28f2..62a0071646 100644 --- a/lib/generators/rspec/scaffold/templates/controller_spec.rb +++ b/lib/generators/rspec/scaffold/templates/controller_spec.rb @@ -23,17 +23,13 @@ # This should return the minimal set of attributes required to create a valid # <%= class_name %>. As you add validations to <%= class_name %>, be sure to - # update the return value of this method accordingly. - def valid_attributes - <%= formatted_hash(example_valid_attributes) %> - end + # adjust the attributes here as well. + let(:valid_attributes) { <%= formatted_hash(example_valid_attributes) %> } # This should return the minimal set of values that should be in the session # in order to pass any filters (e.g. authentication) defined in # <%= controller_class_name %>Controller. Be sure to keep this updated too. - def valid_session - {} - end + let(:valid_session) { {} } <% unless options[:singleton] -%> describe "GET index" do @@ -113,7 +109,7 @@ def valid_session # specifies that the <%= class_name %> created on the previous line # receives the :update_attributes message with whatever params are # submitted in the request. - <%- if Rails.version >= '4' -%> + <%- if RSpec::Rails.rails_version_satisfied_by?('>= 4.0.0.beta1') -%> <%= class_name %>.any_instance.should_receive(:update).with(<%= formatted_hash(example_params_for_update) %>) <%- else -%> <%= class_name %>.any_instance.should_receive(:update_attributes).with(<%= formatted_hash(example_params_for_update) %>) diff --git a/lib/rspec/rails.rb b/lib/rspec/rails.rb index 53047ed6a1..1414b8a839 100644 --- a/lib/rspec/rails.rb +++ b/lib/rspec/rails.rb @@ -5,6 +5,7 @@ c.backtrace_clean_patterns << /lib\/rspec\/rails/ end +require 'rspec/rails/rails_version' require 'rspec/rails/extensions' require 'rspec/rails/view_rendering' require 'rspec/rails/adapters' diff --git a/lib/rspec/rails/adapters.rb b/lib/rspec/rails/adapters.rb index 87470acab0..b4efe1fd68 100644 --- a/lib/rspec/rails/adapters.rb +++ b/lib/rspec/rails/adapters.rb @@ -24,7 +24,8 @@ def assertion_instance end assertion_modules.each do |mod| - mod.instance_methods.each do |method| + mod.public_instance_methods.each do |method| + next if method == :method_missing || method == "method_missing" class_eval <<-EOM, __FILE__, __LINE__ + 1 def #{method}(*args, &block) assertion_instance.send(:#{method}, *args, &block) @@ -36,6 +37,34 @@ def #{method}(*args, &block) end end + # MiniTest::Unit::LifecycleHooks + module MiniTestLifecycleAdapter + extend ActiveSupport::Concern + + included do |group| + group.before { after_setup } + group.after { before_teardown } + + group.around do |example| + before_setup + example.run + after_teardown + end + end + + def before_setup + end + + def after_setup + end + + def before_teardown + end + + def after_teardown + end + end + module SetupAndTeardownAdapter extend ActiveSupport::Concern diff --git a/lib/rspec/rails/example/controller_example_group.rb b/lib/rspec/rails/example/controller_example_group.rb index eaaa028b55..f45e683d40 100644 --- a/lib/rspec/rails/example/controller_example_group.rb +++ b/lib/rspec/rails/example/controller_example_group.rb @@ -11,6 +11,7 @@ module ControllerExampleGroup include RSpec::Rails::Matchers::RedirectTo include RSpec::Rails::Matchers::RenderTemplate include RSpec::Rails::Matchers::RoutingMatchers + include RSpec::Rails::AssertionDelegator.new(ActionDispatch::Assertions::RoutingAssertions) module ClassMethods # @private @@ -64,18 +65,43 @@ def self.name; "AnonymousController"; end metadata[:example_group][:described_class].class_eval(&body) before do - @orig_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new - @routes.draw { resources :anonymous } + @orig_routes = self.routes + self.routes = ActionDispatch::Routing::RouteSet.new.tap { |r| + r.draw { resources :anonymous } + } end after do - @routes, @orig_routes = @orig_routes, nil + self.routes = @orig_routes + @orig_routes = nil + end + end + + # Specifies the routeset that will be used for the example group. This + # is most useful when testing Rails engines. + # + # @example + # + # describe MyEngine::PostsController do + # routes { MyEngine::Engine.routes } + # + # # ... + # end + def routes(&blk) + before do + self.routes = blk.call end end end attr_reader :controller, :routes + # @api private + def routes=(routes) + @routes = routes + assertion_instance.instance_variable_set(:@routes, routes) + end + module BypassRescue def rescue_with_handler(exception) raise exception @@ -107,7 +133,9 @@ def bypass_rescue # If method is a named_route, delegates to the RouteSet associated with # this controller. def method_missing(method, *args, &block) - if @orig_routes && @orig_routes.named_routes.helpers.include?(method) + if @routes && @routes.named_routes.helpers.include?(method) + controller.send(method, *args, &block) + elsif @orig_routes && @orig_routes.named_routes.helpers.include?(method) controller.send(method, *args, &block) else super @@ -120,8 +148,17 @@ def method_missing(method, *args, &block) metadata[:type] = :controller before do - @routes = ::Rails.application.routes - ActionController::Base.allow_forgery_protection = false + self.routes = ::Rails.application.routes + end + + around do |ex| + previous_allow_forgery_protection_value = ActionController::Base.allow_forgery_protection + begin + ActionController::Base.allow_forgery_protection = false + ex.call + ensure + ActionController::Base.allow_forgery_protection = previous_allow_forgery_protection_value + end end end end diff --git a/lib/rspec/rails/example/rails_example_group.rb b/lib/rspec/rails/example/rails_example_group.rb index 28e896f777..392ad6ceb5 100644 --- a/lib/rspec/rails/example/rails_example_group.rb +++ b/lib/rspec/rails/example/rails_example_group.rb @@ -1,12 +1,14 @@ # Temporary workaround to resolve circular dependency between rspec-rails' spec # suite and ammeter. require 'rspec/rails/matchers' +require 'rspec/rails/rails_version' module RSpec module Rails module RailsExampleGroup extend ActiveSupport::Concern include RSpec::Rails::SetupAndTeardownAdapter + include RSpec::Rails::MiniTestLifecycleAdapter if RSpec::Rails.rails_version_satisfied_by?('>= 4.0.0.beta1') include RSpec::Rails::TestUnitAssertionAdapter include RSpec::Rails::Matchers end diff --git a/lib/rspec/rails/example/routing_example_group.rb b/lib/rspec/rails/example/routing_example_group.rb index 005ae26847..b408d4becb 100644 --- a/lib/rspec/rails/example/routing_example_group.rb +++ b/lib/rspec/rails/example/routing_example_group.rb @@ -8,17 +8,43 @@ module RoutingExampleGroup include RSpec::Rails::Matchers::RoutingMatchers::RouteHelpers include RSpec::Rails::AssertionDelegator.new(ActionDispatch::Assertions::RoutingAssertions) + module ClassMethods + # Specifies the routeset that will be used for the example group. This + # is most useful when testing Rails engines. + # + # @example + # + # describe MyEngine::PostsController do + # routes { MyEngine::Engine.routes } + # + # it "routes posts#index" do + # expect(:get => "/posts").to + # route_to(:controller => "my_engine/posts", :action => "index") + # end + # end + def routes(&blk) + before do + self.routes = blk.call + end + end + end + included do metadata[:type] = :routing before do - @routes = ::Rails.application.routes - assertion_instance.instance_variable_set(:@routes, @routes) + self.routes = ::Rails.application.routes end end attr_reader :routes + # @api private + def routes=(routes) + @routes = routes + assertion_instance.instance_variable_set(:@routes, routes) + end + private def method_missing(m, *args, &block) diff --git a/lib/rspec/rails/example/view_example_group.rb b/lib/rspec/rails/example/view_example_group.rb index 737f13d99c..7d33a25226 100644 --- a/lib/rspec/rails/example/view_example_group.rb +++ b/lib/rspec/rails/example/view_example_group.rb @@ -107,7 +107,7 @@ def _default_file_to_render end def _default_render_options - if ::Rails.version >= "3.2" + if RSpec::Rails.rails_version_satisfied_by?('>= 3.2') # pluck the handler, format, and locale out of, eg, posts/index.de.html.haml template, *components = _default_file_to_render.split('.') handler, format, locale = *components.reverse diff --git a/lib/rspec/rails/fixture_support.rb b/lib/rspec/rails/fixture_support.rb index b67738f8c3..17ce7a655e 100644 --- a/lib/rspec/rails/fixture_support.rb +++ b/lib/rspec/rails/fixture_support.rb @@ -4,6 +4,7 @@ module Rails module FixtureSupport extend ActiveSupport::Concern include RSpec::Rails::SetupAndTeardownAdapter + include RSpec::Rails::MiniTestLifecycleAdapter if RSpec::Rails.rails_version_satisfied_by?('>= 4.0.0.beta1') include RSpec::Rails::TestUnitAssertionAdapter include ActiveRecord::TestFixtures diff --git a/lib/rspec/rails/matchers/routing_matchers.rb b/lib/rspec/rails/matchers/routing_matchers.rb index 0330282af3..70f7940d5e 100644 --- a/lib/rspec/rails/matchers/routing_matchers.rb +++ b/lib/rspec/rails/matchers/routing_matchers.rb @@ -97,7 +97,7 @@ def be_routable end module RouteHelpers - %w(get post put delete options head).each do |method| + %w(get post put patch delete options head).each do |method| define_method method do |path| { method.to_sym => path } end diff --git a/lib/rspec/rails/mocks.rb b/lib/rspec/rails/mocks.rb index 76b5957cb2..668256759d 100644 --- a/lib/rspec/rails/mocks.rb +++ b/lib/rspec/rails/mocks.rb @@ -96,7 +96,7 @@ def self.primary_key; :id; end :valid? => true, :blank? => false) - mock("#{model_class.name}_#{stubs[:id]}", stubs).tap do |m| + double("#{model_class.name}_#{stubs[:id]}", stubs).tap do |m| m.singleton_class.class_eval do include ActiveModelInstanceMethods include ActiveRecordInstanceMethods if defined?(ActiveRecord) diff --git a/lib/rspec/rails/rails_version.rb b/lib/rspec/rails/rails_version.rb new file mode 100644 index 0000000000..af00f59029 --- /dev/null +++ b/lib/rspec/rails/rails_version.rb @@ -0,0 +1,17 @@ +module RSpec + module Rails + # @api private + def self.rails_version_satisfied_by?(requirement) + Gem::Requirement.new(requirement).satisfied_by?(rails_version) + end + + # @api private + def self.rails_version + @rails_version ||= if ::Rails.version.is_a?(Gem::Version) + ::Rails.version + else + Gem::Version.new(::Rails.version.to_s) + end + end + end +end diff --git a/lib/rspec/rails/tasks/rspec.rake b/lib/rspec/rails/tasks/rspec.rake index 3a9e543828..f3455a3126 100644 --- a/lib/rspec/rails/tasks/rspec.rake +++ b/lib/rspec/rails/tasks/rspec.rake @@ -25,11 +25,14 @@ namespace :spec do end end - desc "Run all specs with rcov" - RSpec::Core::RakeTask.new(:rcov => spec_prereq) do |t| - t.rcov = true - t.pattern = "./spec/**/*_spec.rb" - t.rcov_opts = '--exclude /gems/,/Library/,/usr/,lib/tasks,.bundle,config,/lib/rspec/,/lib/rspec-,spec' + # RCov task only enabled for Ruby 1.8 + if RUBY_VERSION < '1.9' + desc "Run all specs with rcov" + RSpec::Core::RakeTask.new(:rcov => spec_prereq) do |t| + t.rcov = true + t.pattern = "./spec/**/*_spec.rb" + t.rcov_opts = '--exclude /gems/,/Library/,/usr/,lib/tasks,.bundle,config,/lib/rspec/,/lib/rspec-,spec' + end end task :statsetup do @@ -42,4 +45,3 @@ namespace :spec do end end end - diff --git a/lib/rspec/rails/version.rb b/lib/rspec/rails/version.rb index 2618cca6a3..1056753a24 100644 --- a/lib/rspec/rails/version.rb +++ b/lib/rspec/rails/version.rb @@ -1,7 +1,7 @@ module RSpec module Rails module Version - STRING = '2.13.0' + STRING = '2.13.1' end end end diff --git a/rspec-rails.gemspec b/rspec-rails.gemspec index 2411297c3b..6e7d659031 100644 --- a/rspec-rails.gemspec +++ b/rspec-rails.gemspec @@ -35,7 +35,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake', '~> 10.0.0' s.add_development_dependency 'cucumber', '~> 1.1.9' s.add_development_dependency 'aruba', '~> 0.4.11' - s.add_development_dependency 'ZenTest', '4.6.2' + s.add_development_dependency 'ZenTest', '4.9.0' s.add_development_dependency 'ammeter', '0.2.5' - s.add_development_dependency 'capybara', '>= 2.0.0.beta4' + s.add_development_dependency 'capybara', '>= 2.0.0' end diff --git a/spec/generators/rspec/controller/controller_generator_spec.rb b/spec/generators/rspec/controller/controller_generator_spec.rb index 9a78d672c3..d6f1439cc8 100644 --- a/spec/generators/rspec/controller/controller_generator_spec.rb +++ b/spec/generators/rspec/controller/controller_generator_spec.rb @@ -50,6 +50,17 @@ it { should_not exist } end end + + describe 'with --no-template-engine' do + before do + run_generator %w(posts index --no-template-engine) + end + + describe 'index.html.erb' do + subject { file('spec/views/posts/index.html._spec.rb') } + it { should_not exist } + end + end end describe 'are generated' do diff --git a/spec/generators/rspec/install/install_generator_spec.rb b/spec/generators/rspec/install/install_generator_spec.rb index 63c85b6dd4..52b81abfec 100644 --- a/spec/generators/rspec/install/install_generator_spec.rb +++ b/spec/generators/rspec/install/install_generator_spec.rb @@ -16,7 +16,7 @@ File.read( file('spec/spec_helper.rb') ).should =~ /^require 'rspec\/autorun'$/m end - if ::Rails.version >= '4' + if RSpec::Rails.rails_version_satisfied_by?('>= 4.0.0.beta1') it "generates spec/spec_helper.rb with a check for pending migrations" do run_generator File.read( file('spec/spec_helper.rb') ).should =~ /ActiveRecord::Migration\.check_pending!/m diff --git a/spec/generators/rspec/scaffold/scaffold_generator_spec.rb b/spec/generators/rspec/scaffold/scaffold_generator_spec.rb index b7a4011c8e..b800fd2b15 100644 --- a/spec/generators/rspec/scaffold/scaffold_generator_spec.rb +++ b/spec/generators/rspec/scaffold/scaffold_generator_spec.rb @@ -72,6 +72,29 @@ end end + describe 'with --no-template-engine' do + before { run_generator %w(posts --no-template-engine) } + describe 'edit' do + subject { file("spec/views/posts/edit.html._spec.rb") } + it { should_not exist } + end + + describe 'index' do + subject { file("spec/views/posts/index.html._spec.rb") } + it { should_not exist } + end + + describe 'new' do + subject { file("spec/views/posts/new.html._spec.rb") } + it { should_not exist } + end + + describe 'show' do + subject { file("spec/views/posts/show.html._spec.rb") } + it { should_not exist } + end + end + describe 'with --no-view-specs' do before { run_generator %w(posts --no-view-specs) } diff --git a/spec/rspec/rails/assertion_delegator_spec.rb b/spec/rspec/rails/assertion_delegator_spec.rb index 2956c9e28b..6ccc2df9b0 100644 --- a/spec/rspec/rails/assertion_delegator_spec.rb +++ b/spec/rspec/rails/assertion_delegator_spec.rb @@ -27,4 +27,17 @@ def things expect(klass.new).to have_thing(:a) expect(klass.new).not_to have_thing(:b) end + + it "does not delegate method_missing" do + assertions = Module.new { + def method_missing(method, *args) + end + } + + klass = Class.new { + include RSpec::Rails::AssertionDelegator.new(assertions) + } + + expect { klass.new.abc123 }.to raise_error(NoMethodError) + end end diff --git a/spec/rspec/rails/example/view_example_group_spec.rb b/spec/rspec/rails/example/view_example_group_spec.rb index 79f1e220a6..01ef6f8b7a 100644 --- a/spec/rspec/rails/example/view_example_group_spec.rb +++ b/spec/rspec/rails/example/view_example_group_spec.rb @@ -118,7 +118,7 @@ def _assigns view_spec.stub(:_default_file_to_render) { "widgets/new.en.html.erb" } view_spec.render - if ::Rails.version >= "3.2" + if RSpec::Rails.rails_version_satisfied_by?('>= 3.2') view_spec.received.first.should == [{:template => "widgets/new", :locales=>['en'], :formats=>['html'], :handlers=>['erb']}, {}, nil] else view_spec.received.first.should == [{:template => "widgets/new.en.html.erb"}, {}, nil] diff --git a/spec/rspec/rails/matchers/have_rendered_spec.rb b/spec/rspec/rails/matchers/have_rendered_spec.rb index 1339d9c43d..8a216ee79d 100644 --- a/spec/rspec/rails/matchers/have_rendered_spec.rb +++ b/spec/rspec/rails/matchers/have_rendered_spec.rb @@ -29,7 +29,7 @@ context "with should" do context "when assert_template passes" do it "passes" do - self.stub!(:assert_template) + def assert_template(*); end expect do expect(response).to send(template_expectation, "template_name") end.to_not raise_exception @@ -38,7 +38,7 @@ context "when assert_template fails" do it "uses failure message from assert_template" do - self.stub!(:assert_template) do + def assert_template(*) raise ActiveSupport::TestCase::Assertion.new("this message") end expect do @@ -49,7 +49,7 @@ context "when fails due to some other exception" do it "raises that exception" do - self.stub!(:assert_template) do + def assert_template(*) raise "oops" end expect do diff --git a/spec/rspec/rails/matchers/relation_match_array_spec.rb b/spec/rspec/rails/matchers/relation_match_array_spec.rb index 99ecdaaded..db40b336e1 100644 --- a/spec/rspec/rails/matchers/relation_match_array_spec.rb +++ b/spec/rspec/rails/matchers/relation_match_array_spec.rb @@ -5,7 +5,7 @@ let!(:models) { Array.new(3) { MockableModel.create } } - if Rails.version >= '4' + if RSpec::Rails.rails_version_satisfied_by?('>= 4.0.0.beta1') it "verifies that the scope returns the records on the right hand side, regardless of order" do MockableModel.all.should =~ models.reverse end diff --git a/spec/rspec/rails/matchers/route_to_spec.rb b/spec/rspec/rails/matchers/route_to_spec.rb index 22c1607fe3..377a68ea14 100644 --- a/spec/rspec/rails/matchers/route_to_spec.rb +++ b/spec/rspec/rails/matchers/route_to_spec.rb @@ -57,7 +57,6 @@ def assert_recognizes(*) context "with should" do context "when assert_recognizes passes" do it "passes" do - self.stub!(:assert_recognizes) expect do {:get => "path"}.should route_to("these" => "options") end.to_not raise_exception @@ -66,7 +65,7 @@ def assert_recognizes(*) context "when assert_recognizes fails with an assertion failure" do it "fails with message from assert_recognizes" do - self.stub!(:assert_recognizes) do + def assert_recognizes(*) raise ActiveSupport::TestCase::Assertion.new("this message") end expect do @@ -77,7 +76,7 @@ def assert_recognizes(*) context "when assert_recognizes fails with a routing error" do it "fails with message from assert_recognizes" do - self.stub!(:assert_recognizes) do + def assert_recognizes(*) raise ActionController::RoutingError.new("this message") end expect do @@ -88,7 +87,7 @@ def assert_recognizes(*) context "when an exception is raised" do it "raises that exception" do - self.stub!(:assert_recognizes) do + def assert_recognizes(*) raise "oops" end expect do @@ -101,7 +100,6 @@ def assert_recognizes(*) context "with should_not" do context "when assert_recognizes passes" do it "fails with custom message" do - self.stub!(:assert_recognizes) expect do {:get => "path"}.should_not route_to("these" => "options") end.to raise_error(/expected .* not to route to .*/) @@ -110,7 +108,7 @@ def assert_recognizes(*) context "when assert_recognizes fails with an assertion failure" do it "passes" do - self.stub!(:assert_recognizes) do + def assert_recognizes(*) raise ActiveSupport::TestCase::Assertion.new("this message") end expect do @@ -121,7 +119,7 @@ def assert_recognizes(*) context "when assert_recognizes fails with a routing error" do it "passes" do - self.stub!(:assert_recognizes) do + def assert_recognizes(*) raise ActionController::RoutingError.new("this message") end expect do @@ -132,7 +130,7 @@ def assert_recognizes(*) context "when an exception is raised" do it "raises that exception" do - self.stub!(:assert_recognizes) do + def assert_recognizes(*) raise "oops" end expect do @@ -143,8 +141,9 @@ def assert_recognizes(*) end it "uses failure message from assert_recognizes" do - self.stub!(:assert_recognizes).and_raise( - ActiveSupport::TestCase::Assertion.new("this message")) + def assert_recognizes(*) + raise ActiveSupport::TestCase::Assertion, "this message" + end expect do {"this" => "path"}.should route_to("these" => "options") end.to raise_error("this message") diff --git a/spec/rspec/rails/minitest_lifecycle_adapter_spec.rb b/spec/rspec/rails/minitest_lifecycle_adapter_spec.rb new file mode 100644 index 0000000000..66b4caaf2a --- /dev/null +++ b/spec/rspec/rails/minitest_lifecycle_adapter_spec.rb @@ -0,0 +1,22 @@ +require "spec_helper" + +describe RSpec::Rails::MiniTestLifecycleAdapter do + it "invokes minitest lifecycle hooks at the appropriate times" do + invocations = [] + example_group = RSpec::Core::ExampleGroup.describe("MiniTestHooks") do + include RSpec::Rails::MiniTestLifecycleAdapter + + define_method(:before_setup) { invocations << :before_setup } + define_method(:after_setup) { invocations << :after_setup } + define_method(:before_teardown) { invocations << :before_teardown } + define_method(:after_teardown) { invocations << :after_teardown } + end + + example = example_group.example("foo") { invocations << :example } + example_group.run(NullObject.new) + + expect(invocations).to eq([ + :before_setup, :after_setup, :example, :before_teardown, :after_teardown + ]) + end +end diff --git a/spec/rspec/rails/rails_version_spec.rb b/spec/rspec/rails/rails_version_spec.rb new file mode 100644 index 0000000000..77dd908f06 --- /dev/null +++ b/spec/rspec/rails/rails_version_spec.rb @@ -0,0 +1,29 @@ +require "spec_helper" + +describe RSpec::Rails, "version" do + def clear_memoized_version + if RSpec::Rails.instance_variable_defined?(:@rails_version) + RSpec::Rails.send(:remove_instance_variable, :@rails_version) + end + end + + before { clear_memoized_version } + after { clear_memoized_version } + + describe "#rails_version_satisfied_by?" do + it "checks whether the gem version constraint is satisfied by the Rails version" do + ::Rails.stub(:version).and_return(Gem::Version.new("4.0.0")) + + expect(RSpec::Rails.rails_version_satisfied_by?(">=3.2.0")).to be_true + expect(RSpec::Rails.rails_version_satisfied_by?("~>4.0.0")).to be_true + expect(RSpec::Rails.rails_version_satisfied_by?("~>3.2.0")).to be_false + end + + it "operates correctly when the Rails version is a string (pre-Rails 4.0)" do + ::Rails.stub(:version).and_return("3.2.1") + + expect(RSpec::Rails.rails_version_satisfied_by?("~>3.2.0")).to be_true + expect(RSpec::Rails.rails_version_satisfied_by?("~>3.1.0")).to be_false + end + end +end diff --git a/spec/support/null_object.rb b/spec/support/null_object.rb new file mode 100644 index 0000000000..8422d0828b --- /dev/null +++ b/spec/support/null_object.rb @@ -0,0 +1,6 @@ +class NullObject + private + + def method_missing(method, *args, &blk) + end +end diff --git a/templates/generate_stuff.rb b/templates/generate_stuff.rb index 95b9a22b21..929711d6f9 100644 --- a/templates/generate_stuff.rb +++ b/templates/generate_stuff.rb @@ -8,11 +8,11 @@ generate('scaffold widget name:string category:string instock:boolean foo_id:integer bar_id:integer --force') generate('observer widget') generate('scaffold gadget') # scaffold with no attributes -generate('scaffold admin/accounts name:string') # scaffold with nested resource +generate('scaffold admin/account name:string') # scaffold with nested resource generate('controller things custom_action') file "app/views/things/custom_action.html.erb", "This is a template for a custom action.", {:force=>true} run('rake db:migrate') -run('rake db:test:prepare') +run('rake db:migrate RAILS_ENV=test')