diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 70% rename from ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug_report.md index 6a3d519e6d..151369173a 100644 --- a/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,15 +1,16 @@ - @@ -18,7 +19,7 @@ resolve your bug faster! Ruby version: Rails version: -Rspec version: +RSpec version: + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.rubocop_rspec_base.yml b/.rubocop_rspec_base.yml index 3130e26766..af024e8c53 100644 --- a/.rubocop_rspec_base.yml +++ b/.rubocop_rspec_base.yml @@ -1,4 +1,4 @@ -# This file was generated on 2019-01-03T20:34:23+00:00 from the rspec-dev repo. +# This file was generated on 2019-07-24T15:33:53+02:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. # This file contains defaults for RSpec projects. Individual projects diff --git a/.travis.yml b/.travis.yml index a71e6c4187..a1cc8fe9ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: ruby -sudo: false - # We deviate from the rspec-dev cache setting here. # # Travis has special bundler support where it knows to run `bundle clean` @@ -34,16 +32,23 @@ before_script: script: "script/run_build 2>&1" +# In order to install old Rubies, we need to use old Ubuntu distibution. +dist: trusty + matrix: include: # Rails dev / 6 builds >= 2.4.4 - - rvm: 2.5.1 + - rvm: 2.6.3 + env: RAILS_VERSION=master + - rvm: 2.5.3 env: RAILS_VERSION=master - rvm: 2.4.4 env: RAILS_VERSION=master # Rails 5.2 builds >= 2.2.2 - - rvm: 2.5.1 + - rvm: 2.6.3 + env: RAILS_VERSION='~> 5.2.0' + - rvm: 2.5.3 env: RAILS_VERSION='~> 5.2.0' - rvm: 2.4.4 env: RAILS_VERSION='~> 5.2.0' @@ -53,7 +58,9 @@ matrix: env: RAILS_VERSION='~> 5.2.0' # Rails 5.1 Builds >= 2.2.2 - - rvm: 2.5.1 + - rvm: 2.6.3 + env: RAILS_VERSION='~> 5.1.0' + - rvm: 2.5.3 env: RAILS_VERSION='~> 5.1.0' - rvm: 2.4.4 env: RAILS_VERSION='~> 5.1.0' @@ -63,7 +70,9 @@ matrix: env: RAILS_VERSION='~> 5.1.0' # Rails 5.0 Builds >= 2.2.2 - - rvm: 2.5.1 + - rvm: 2.6.3 + env: RAILS_VERSION='~> 5.0.0' + - rvm: 2.5.3 env: RAILS_VERSION='~> 5.0.0' - rvm: 2.4.4 env: RAILS_VERSION='~> 5.0.0' @@ -193,7 +202,9 @@ matrix: env: RAILS_VERSION='~> 3.0.20' allow_failures: - - rvm: 2.5.1 + - rvm: 2.6.3 + env: RAILS_VERSION=master + - rvm: 2.5.3 env: RAILS_VERSION=master - rvm: 2.4.4 env: RAILS_VERSION=master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4af94b7a5..161b63008d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,8 +11,8 @@ We welcome contributions from *everyone*. While contributing, please follow the If you'd like to help make RSpec better, here are some ways you can contribute: - by running RSpec HEAD to help us catch bugs before new releases - - by [reporting bugs you encounter](https://github.com/rspec/rspec-rails/issues/new) - - by [suggesting new features](https://github.com/rspec/rspec-rails/issues/new) + - by [reporting bugs you encounter](https://github.com/rspec/rspec-rails/issues/new?template=bug_report.md) + - by [suggesting new features](https://github.com/rspec/rspec-rails/issues/new?template=feature_request.md) - by improving RSpec's [Relish](https://relishapp.com/rspec) or [API](http://rspec.info/documentation/) documentation - by improving [RSpec's website](http://rspec.info/) ([source](https://github.com/rspec/rspec.github.io)) - by taking part in [feature and issue discussions](https://github.com/rspec/rspec-rails/issues) diff --git a/Changelog.md b/Changelog.md index 634076fc3d..7a1713d09d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,13 @@ -### Development +### 3.8.3 / 2019-10-03 [Full Changelog](http://github.com/rspec/rspec-rails/compare/v3.8.2...master) +Bug Fixes: + +* Namespaced fixtures now generate a `/` seperated path rather than an `_`. + (@nxlith, #2077) +* Check the arity of `errors` before attempting to use it to generate the `be_valid` + error message. (Kevin Kuchta, #2096) + ### 3.8.2 / 2019-01-13 [Full Changelog](http://github.com/rspec/rspec-rails/compare/v3.8.1...v3.8.2) diff --git a/Gemfile b/Gemfile index 0ce81a8196..03852dce93 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,10 @@ end if RUBY_VERSION < '1.9' gem 'ffi', '< 1.9.19' # ffi dropped Ruby 1.8 support in 1.9.19 +else + gem 'ffi', '~> 1.9.25' end + if RUBY_VERSION >= '2.0.0' gem 'rake', '>= 10.0.0' elsif RUBY_VERSION >= '1.9.3' @@ -44,6 +47,10 @@ if RUBY_VERSION < '2.0.0' gem 'mime-types', '< 3' end +# Version 5.12 of minitest requires Ruby 2.4 +if RUBY_VERSION < '2.4.0' + gem 'minitest', '< 5.12.0' +end # Capybara versions that support RSpec 3 only support RUBY_VERSION >= 1.9.3 if RUBY_VERSION >= '1.9.3' @@ -63,14 +70,18 @@ elsif RUBY_VERSION < '1.9.3' gem 'nokogiri', '1.5.2' elsif RUBY_VERSION < '2.1.0' gem 'nokogiri', '1.6.8.1' -else +elsif RUBY_VERSION < '2.3.0' gem 'nokogiri', '1.8.5' +else + gem 'nokogiri', '~> 1.10' end if RUBY_VERSION <= '1.8.7' # cucumber and gherkin require rubyzip as a runtime dependency on 1.8.7 # Only < 1.0 supports 1.8.7 gem 'rubyzip', '< 1.0' +else + gem "rubyzip", '>= 1.2.2' end if RUBY_VERSION >= '2.0.0' && RUBY_VERSION < '2.2.0' diff --git a/Gemfile-rails-dependencies b/Gemfile-rails-dependencies index 3eb2f2f759..a849eeeda4 100644 --- a/Gemfile-rails-dependencies +++ b/Gemfile-rails-dependencies @@ -1,20 +1,20 @@ version_file = File.expand_path("../.rails-version", __FILE__) -case version = ENV['RAILS_VERSION'] || (File.exist?(version_file) && File.read(version_file).chomp) +case version = ENV['RAILS_VERSION'] || (File.exist?(version_file) && File.read(version_file).chomp) || '' when /master/ - gem "rails", :git => "git://github.com/rails/rails.git" - gem "arel", :git => "git://github.com/rails/arel.git" - gem "journey", :git => "git://github.com/rails/journey.git" - gem "activerecord-deprecated_finders", :git => "git://github.com/rails/activerecord-deprecated_finders.git" - gem "rails-observers", :git => "git://github.com/rails/rails-observers" - gem "web-console", :git => "git://github.com/rails/web-console", :group => :development - gem 'sass-rails', :git => "git://github.com/rails/sass-rails.git" - gem 'coffee-rails', :git => "git://github.com/rails/coffee-rails.git" - gem 'rack', :git => 'git://github.com/rack/rack.git' - gem 'i18n', :git => 'git://github.com/svenfuchs/i18n.git', :branch => 'master' - gem 'sprockets', :git => 'git://github.com/rails/sprockets.git', :branch => 'master' - gem 'sprockets-rails', :git => 'git://github.com/rails/sprockets-rails.git', :branch => 'master' + gem "rails", :git => "https://github.com/rails/rails.git" + gem "arel", :git => "https://github.com/rails/arel.git" + gem "journey", :git => "https://github.com/rails/journey.git" + gem "activerecord-deprecated_finders", :git => "https://github.com/rails/activerecord-deprecated_finders.git" + gem "rails-observers", :git => "https://github.com/rails/rails-observers" + gem "web-console", :git => "https://github.com/rails/web-console", :group => :development + gem 'sass-rails', :git => "https://github.com/rails/sass-rails.git" + gem 'coffee-rails', :git => "https://github.com/rails/coffee-rails.git" + gem 'rack', :git => 'https://github.com/rack/rack.git' + gem 'i18n', :git => 'https://github.com/svenfuchs/i18n.git', :branch => 'master' + gem 'sprockets', :git => 'https://github.com/rails/sprockets.git', :branch => 'master' + gem 'sprockets-rails', :git => 'https://github.com/rails/sprockets-rails.git', :branch => 'master' if RUBY_VERSION >= "2.2" - gem 'puma', :git => 'git://github.com/puma/puma', :branch => 'master' + gem 'puma', :git => 'https://github.com/puma/puma', :branch => 'master' end when /stable$/ gem_list = %w[rails railties actionmailer actionpack activerecord activesupport] @@ -25,7 +25,7 @@ when /stable$/ end gem_list.each do |rails_gem| - gem rails_gem, :git => "git://github.com/rails/rails.git", :branch => version + gem rails_gem, :git => "https://github.com/rails/rails.git", :branch => version end when nil, false, "" if RUBY_VERSION < '1.9.3' diff --git a/Gemfile-rspec-dependencies b/Gemfile-rspec-dependencies index 95abe47a64..c2abb466ce 100644 --- a/Gemfile-rspec-dependencies +++ b/Gemfile-rspec-dependencies @@ -4,6 +4,6 @@ branch = File.read(File.expand_path("../maintenance-branch", __FILE__)).chomp if File.exist?(library_path) && !ENV['USE_GIT_REPOS'] gem lib, :path => library_path, :require => false else - gem lib, :git => "git://github.com/rspec/#{lib}.git", :branch => branch, :require => false + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => branch, :require => false end end diff --git a/README.md b/README.md index fbeedecf3d..0484f8cda2 100644 --- a/README.md +++ b/README.md @@ -1,602 +1,370 @@ -# rspec-rails [![Build Status](https://secure.travis-ci.org/rspec/rspec-rails.svg?branch=master)](http://travis-ci.org/rspec/rspec-rails) [![Code Climate](https://img.shields.io/codeclimate/github/rspec/rspec-rails.svg)](https://codeclimate.com/github/rspec/rspec-rails) -**rspec-rails** is a testing framework for Rails 3.x, 4.x and 5.x. +# rspec-rails [![Build Status][]][travis-ci] [![Code Climate][]][code-climate] [![Gem Version][]](gem-version) -Use **[rspec-rails 1.x](http://github.com/dchelimsky/rspec-rails)** for Rails -2.x. +`rspec-rails` brings the [RSpec][] testing framework to [Ruby on Rails][] +as a drop-in alternative to its default testing framework, Minitest. -## Installation +In RSpec, tests are not just scripts that verify your application code. +They’re also specifications (or _specs,_ for short): +detailed explanations of how the application is supposed to behave, +expressed in plain English. -Add `rspec-rails` to **both** the `:development` and `:test` groups in the -`Gemfile`: +Use **[`rspec-rails` 1.x][]** for Rails 2.x. -```ruby -group :development, :test do - gem 'rspec-rails', '~> 3.7' -end -``` +[Build Status]: https://secure.travis-ci.org/rspec/rspec-rails.svg?branch=master +[travis-ci]: https://travis-ci.org/rspec/rspec-rails +[Code Climate]: https://codeclimate.com/github/rspec/rspec-rails.svg +[code-climate]: https://codeclimate.com/github/rspec/rspec-rails +[Gem Version]: https://badge.fury.io/rb/rspec-rails.svg +[gem-version]: https://badge.fury.io/rb/rspec-rails +[RSpec]: https://rspec.info/ +[Ruby on Rails]: https://rubyonrails.org/ +[`rspec-rails` 1.x]: https://github.com/dchelimsky/rspec-rails -Want to run against the `master` branch? You'll need to include the dependent -RSpec repos as well. Add the following to your `Gemfile`: +## Installation -```ruby -%w[rspec-core rspec-expectations rspec-mocks rspec-rails rspec-support].each do |lib| - gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'master' -end -``` +1. Add `rspec-rails` to **both** the `:development` and `:test` groups + of your app’s `Gemfile`: -Download and install by running: + ```ruby + # Run against the latest stable release + group :development, :test do + gem 'rspec-rails', '~> 3.8' + end -``` -bundle install -``` + # Or, run against the master branch + # (requires master-branch versions of all related RSpec libraries) + group :development, :test do + %w[rspec-core rspec-expectations rspec-mocks rspec-rails rspec-support].each do |lib| + gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'master' + end + end + ``` -Initialize the `spec/` directory (where specs will reside) with: + (Adding it to the `:development` group is not strictly necessary, + but without it, generators and rake tasks must be preceded by `RAILS_ENV=test`.) -``` -rails generate rspec:install -``` +2. Then, in your project directory: -This adds the following files which are used for configuration: + ```sh + # Download and install + $ bundle install -- `.rspec` -- `spec/spec_helper.rb` -- `spec/rails_helper.rb` + # Generate boilerplate configuration files + # (check the comments in each generated file for more information) + $ rails generate rspec:install + create .rspec + create spec + create spec/spec_helper.rb + create spec/rails_helper.rb + ``` -Check the comments in each file for more information. +## Upgrading -Use the `rspec` command to run your specs: +If your project is already using an older version of `rspec-rails`, +upgrade to the latest version with: -``` -bundle exec rspec +```sh +$ bundle update rspec-rails ``` -By default the above will run all `_spec.rb` files in the `spec` directory. For -more details about this see the [RSpec spec file -docs](https://www.relishapp.com/rspec/rspec-core/docs/spec-files). +RSpec follows [semantic versioning](https://semver.org/), +which means that “major version” upgrades (_e.g.,_ 2.x → 3.x) +come with **breaking changes**. +If you’re upgrading from version 2.x or below, +read the [`rspec-rails` upgrade notes][] to find out what to watch out for. -To run only a subset of these specs use the following command: +Be sure to check the general [RSpec upgrade notes][] as well. -``` -# Run only model specs -bundle exec rspec spec/models +[`rspec-rails` upgrade notes]: https://www.relishapp.com/rspec/rspec-rails/docs/upgrade +[RSpec upgrade notes]: https://relishapp.com/rspec/docs/upgrade -# Run only specs for AccountsController -bundle exec rspec spec/controllers/accounts_controller_spec.rb +## Usage -# Run only spec on line 8 of AccountsController -bundle exec rspec spec/controllers/accounts_controller_spec.rb:8 -``` +### Creating boilerplate specs with `rails generate` -Specs can also be run via `rake spec`, though this command may be slower to -start than the `rspec` command. +```sh +# RSpec hooks into built-in generators +$ rails generate model user + invoke active_record + create db/migrate/20181017040312_create_users.rb + create app/models/user.rb + invoke rspec + create spec/models/user_spec.rb -In Rails 4/5+, you may want to create a binstub for the `rspec` command so it can -be run via `bin/rspec`: +# RSpec also provides its own spec file generators +$ rails generate rspec:model user + create spec/models/user_spec.rb +# List all RSpec generators +$ rails generate --help | grep rspec ``` -bundle binstubs rspec-core -``` - -### Upgrade Note - -For detailed information on the general RSpec 3.x upgrade process see the -[RSpec Upgrade docs](https://relishapp.com/rspec/docs/upgrade). - -There are three particular `rspec-rails` specific changes to be aware of: -1. [The default helper files created in RSpec 3.x have changed](https://www.relishapp.com/rspec/rspec-rails/docs/upgrade#default-helper-files) -2. [File-type inference disabled by default](https://www.relishapp.com/rspec/rspec-rails/docs/upgrade#file-type-inference-disabled) -3. [Rails 4.x `ActiveRecord::Migration` pending migration checks](https://www.relishapp.com/rspec/rspec-rails/docs/upgrade#pending-migration-checks) -4. Extraction of `stub_model` and `mock_model` to - [`rspec-activemodel-mocks`](https://github.com/rspec/rspec-activemodel-mocks) -5. In Rails 5.x, controller testing has been moved to its own gem which is [rails-controller-testing](https://github.com/rails/rails-controller-testing). Using `assigns` in your controller specs without adding this gem will no longer work. -6. `rspec-rails` now includes two helpers, `spec_helper.rb` and `rails_helper.rb`. - `spec_helper.rb` is the conventional RSpec configuration helper, whilst the - Rails specific loading and bootstrapping has moved to the `rails_helper.rb` - file. Rails specs now need this file required beforehand either at the top - of the specific file (recommended) or a common configuration location such - as your `.rspec` file. +### Running specs -Please see the [RSpec Rails Upgrade -docs](https://www.relishapp.com/rspec/rspec-rails/docs/upgrade) for full -details. +```sh +# Default: Run all spec files (i.e., those matching spec/**/*_spec.rb) +$ bundle exec rspec -**NOTE:** Generators run in RSpec 3.x will now require `rails_helper` instead -of `spec_helper`. +# Run all spec files in a single directory (recursively) +$ bundle exec rspec spec/models -### Generators +# Run a single spec file +$ bundle exec rspec spec/controllers/accounts_controller_spec.rb -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. +# Run a single example from a spec file (by line number) +$ bundle exec rspec spec/controllers/accounts_controller_spec.rb:8 -You may also invoke RSpec generators independently. For instance, -running `rails generate rspec:model` will generate a model spec. For more -information, see [list of all -generators](https://www.relishapp.com/rspec/rspec-rails/docs/generators). - -## Contributing - -Once you've set up the environment, you'll need to cd into the working -directory of whichever repo you want to work in. From there you can run the -specs and cucumber features, and make patches. - -NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You -can treat each RSpec repo as an independent project. -Please see the following files: - -For `rspec-rails`-specific development information, see - -- [Build details](BUILD_DETAIL.md) -- [Code of Conduct](CODE_OF_CONDUCT.md) -- [Detailed contributing guide](CONTRIBUTING.md) -- [Development setup guide](DEVELOPMENT.md) - - -## Model Specs - -Use model specs to describe behavior of models (usually ActiveRecord-based) in -the application. - -Model specs default to residing in the `spec/models` folder. Tagging any -context with the metadata `:type => :model` treats its examples as model -specs. - -For example: - -```ruby -require "rails_helper" - -RSpec.describe Post, :type => :model do - context "with 2 or more comments" do - it "orders them in reverse chronologically" do - post = Post.create! - comment1 = post.comments.create!(:body => "first comment") - comment2 = post.comments.create!(:body => "second comment") - expect(post.reload.comments).to eq([comment2, comment1]) - end - end -end +# See all options for running specs +$ bundle exec rspec --help ``` -For more information, see [cucumber scenarios for model -specs](https://www.relishapp.com/rspec/rspec-rails/docs/model-specs). +**Optional:** If `bundle exec rspec` is too verbose for you, +you can generate a binstub at `bin/rspec` +and use that instead (Rails 4+ only): -## Request Specs + ```sh + $ bundle binstubs rspec-core + ``` -Use request specs to describe the client-facing behavior of the application — -specifically, the HTTP response to be issued for a given request (a.k.a. -integration tests). Since such client-facing behavior encompasses controller -actions, this is the type of spec to use for controller testing. +## RSpec DSL Basics (or, how do I write a spec?) -Request specs default to residing in the `spec/requests`, `spec/api`, and -`spec/integration` directories. Tagging any context with the metadata `:type => -:request` treats its examples as request specs. - -Request specs mix in behavior from -[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). +In RSpec, application behavior is described +**first in (almost) plain English, then again in test code**, like so: ```ruby -require 'rails_helper' - -RSpec.describe "home page", :type => :request do - it "displays the user's username after successful login" do - user = User.create!(:username => "jdoe", :password => "secret") - get "/login" - assert_select "form.login" do - assert_select "input[name=?]", "username" - assert_select "input[name=?]", "password" - assert_select "input[type=?]", "submit" +RSpec.describe 'Post' do # + context 'before publication' do # (almost) plain English + it 'cannot have comments' do # + expect { Post.create.comments.create! }.to raise_error(ActiveRecord::RecordInvalid) # test code end - - post "/login", :username => "jdoe", :password => "secret" - assert_select ".header .username", :text => "jdoe" - end -end -``` - -The above example uses only standard Rails and RSpec APIs, but many -RSpec/Rails users like to use extension libraries like -[FactoryBot](https://github.com/thoughtbot/factory_bot) and -[Capybara](https://github.com/jnicklas/capybara): - -```ruby -require 'rails_helper' - -RSpec.describe "home page", :type => :request do - it "displays the user's username after successful login" do - user = FactoryBot.create(:user, :username => "jdoe", :password => "secret") - visit "/login" - fill_in "Username", :with => "jdoe" - fill_in "Password", :with => "secret" - click_button "Log in" - - expect(page).to have_selector(".header .username", :text => "jdoe") end end ``` -FactoryBot decouples this example from changes to validation requirements, -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. 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 -FactoryBot and Capybara seem to be the most widely used. Whether you choose -these or other libs, we strongly recommend using something for each of these -roles. - -For more information, see [cucumber scenarios for request -specs](https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec). - -## Controller Specs - -Controller specs can be used to describe the behavior of Rails controllers. As -of version 3.5, however, controller specs are discouraged in favor of request -specs (which also focus largely on controllers, but capture other critical -aspects of application behavior as well). Controller specs will continue to be -supported until at least version 4.0 (see the [release -notes](http://rspec.info/blog/2016/07/rspec-3-5-has-been-released/#rails-support-for-rails-5) -for details). - -For more information, see [cucumber scenarios for controller -specs](https://www.relishapp.com/rspec/rspec-rails/docs/controller-specs). - -## Feature Specs - -Feature specs test your application from the outside by simulating a browser. -[`capybara`](https://github.com/jnicklas/capybara) is used to manage the -simulated browser. - -Feature specs default to residing in the `spec/features` folder. Tagging any -context with the metadata `:type => :feature` treats its examples as feature -specs. +Running `rspec` will execute this test code, +and then use the plain-English descriptions +to generate a report of where the application +conforms to (or fails to meet) the spec: -Feature specs mix in functionality from the capybara gem, thus they require -`capybara` to use. To use feature specs, add `capybara` to the `Gemfile`: - -```ruby -gem "capybara" ``` +$ rspec --format documentation spec/models/post_spec.rb -For more information, see the [cucumber scenarios for feature -specs](https://www.relishapp.com/rspec/rspec-rails/v/3-4/docs/feature-specs/feature-spec). - -## Mailer specs +Post + before publication + cannot have comments -By default Mailer specs reside in the `spec/mailers` folder. Adding the metadata -`:type => :mailer` to any context makes its examples be treated as mailer specs. +Failures: -`ActionMailer::TestCase::Behavior` is mixed into your mailer specs. - -```ruby -require "rails_helper" + 1) Post before publication cannot have comments + Failure/Error: expect { Post.create.comments.create! }.to raise_error(ActiveRecord::RecordInvalid) + expected ActiveRecord::RecordInvalid but nothing was raised + # ./spec/models/post.rb:4:in `block (3 levels) in ' -RSpec.describe Notifications, :type => :mailer do - describe "notify" do - let(:mail) { Notifications.signup } +Finished in 0.00527 seconds (files took 0.29657 seconds to load) +1 example, 1 failure - it "renders the headers" do - expect(mail.subject).to eq("Signup") - expect(mail.to).to eq(["to@example.org"]) - expect(mail.from).to eq(["from@example.com"]) - end +Failed examples: - it "renders the body" do - expect(mail.body.encoded).to match("Hi") - end - end -end +rspec ./spec/models/post_spec.rb:3 # Post before publication cannot have comments ``` -For more information, see the [cucumber scenarios for mailer specs -](https://relishapp.com/rspec/rspec-rails/v/3-4/docs/mailer-specs). - -## Job specs - -Tagging a context with the metadata `:type => :job` treats its examples as job -specs. Typically these specs will live in `spec/jobs`. - -```ruby -require 'rails_helper' - -RSpec.describe UploadBackupsJob, :type => :job do - describe "#perform_later" do - it "uploads a backup" do - ActiveJob::Base.queue_adapter = :test - UploadBackupsJob.perform_later('backup') - expect(UploadBackupsJob).to have_been_enqueued - end - end -end -``` - -For more information, see the [cucumber scenarios for job specs -](https://relishapp.com/rspec/rspec-rails/docs/job-specs). - -## View specs +For an in-depth look at the RSpec DSL, including lots of examples, +read the official Cucumber documentation for [RSpec Core][]. -View specs default to residing in the `spec/views` folder. Tagging any context -with the metadata `:type => :view` treats its examples as view specs. +[RSpec Core]: https://relishapp.com/rspec/rspec-core/docs -View specs mix in `ActionView::TestCase::Behavior`. +### Helpful Rails Matchers -```ruby -require 'rails_helper' +In RSpec, assertions are called _expectations,_ +and every expectation is built around a _matcher._ +When you `expect(a).to eq(b)`, you’re using the `eq` matcher. -RSpec.describe "events/index", :type => :view do - it "renders _event partial for each event" do - assign(:events, [double(Event), double(Event)]) - render - expect(view).to render_template(:partial => "_event", :count => 2) - end -end +In addition to [the matchers that come standard in RSpec][], +here are some extras that make it easier +to test the various parts of a Rails system: -RSpec.describe "events/show", :type => :view do - it "displays the event location" do - assign(:event, Event.new(:location => "Chicago")) - render - expect(rendered).to include("Chicago") - end -end -``` +| RSpec matcher | Delegates to | Available in | Notes | +| ------------------------ | ----------------- | ------------------------------- | -------------------------------------------------------- | +| [`be_a_new`][] | | all | primarily intended for controller specs | +| [`render_template`][] | `assert_template` | request / controller / view | use with `expect(response).to` | +| [`redirect_to`][] | `assert_redirect` | request / controller | use with `expect(response).to` | +| [`route_to`] | `assert_routing` | routing / controller | replaces `route_for` from version 1.x | +| [`be_routable`] | | routing / controller | usu. for `expect(...).not_to be_routable` | +| [`have_http_status`][] | | request / controller / feature | | +| [`match_array`][] | | all | for comparing arrays of ActiveRecord objects | +| [`have_been_enqueued`][] | | all | requires config: `ActiveJob::Base.queue_adapter = :test` | +| [`have_enqueued_job`][] | | all | requires config: `ActiveJob::Base.queue_adapter = :test` | -View specs infer the controller name and path from the path to the view -template. e.g. if the template is `events/index.html.erb` then: +Follow the links above for examples of how each matcher is used. -```ruby -controller.controller_path == "events" -controller.request.path_parameters[:controller] == "events" -``` +[the matchers that come standard in RSpec]: https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers +[`be_a_new`]: https://relishapp.com/rspec/rspec-rails/docs/matchers/be-a-new-matcher +[`render_template`]: https://relishapp.com/rspec/rspec-rails/docs/matchers/render-template-matcher +[`redirect_to`]: https://relishapp.com/rspec/rspec-rails/docs/matchers/redirect-to-matcher +[`route_to`]: https://relishapp.com/rspec/rspec-rails/docs/routing-specs/route-to-matcher +[`be_routable`]: https://relishapp.com/rspec/rspec-rails/docs/routing-specs/be-routable-matcher +[`have_http_status`]: https://relishapp.com/rspec/rspec-rails/docs/matchers/have-http-status-matcher +[`match_array`]: https://relishapp.com/rspec/rspec-rails/docs/matchers/activerecord-relation-match-array +[`have_been_enqueued`]: https://relishapp.com/rspec/rspec-rails/docs/matchers/have-been-enqueued-matcher +[`have_enqueued_job`]: https://relishapp.com/rspec/rspec-rails/docs/matchers/have-enqueued-job-matcher -This means that most of the time you don't need to set these values. When -spec'ing a partial that is included across different controllers, you _may_ -need to override these values before rendering the view. +### What else does RSpec Rails add? -To provide a layout for the render, you'll need to specify _both_ the template -and the layout explicitly. For example: +For a comprehensive look at RSpec Rails’ features, +read the [official Cucumber documentation][]. -```ruby -render :template => "events/show", :layout => "layouts/application" -``` +[official Cucumber documentation]: https://relishapp.com/rspec/rspec-rails/docs -### `assign(key, val)` +## What tests should I write? -Use this to assign values to instance variables in the view: +RSpec Rails defines ten different _types_ of specs +for testing different parts of a typical Rails application. +Each one inherits from one of Rails’ built-in `TestCase` classes, +meaning the helper methods provided by default in Rails tests +are available in RSpec, as well. -```ruby -assign(:widget, Widget.new) -render -``` + | Spec type | Corresponding Rails test class | + | -------------- | -------------------------------- | + | [model][] | | + | [controller][] | [`ActionController::TestCase`][] | + | [mailer][] | `ActionMailer::TestCase` | + | [job][] | | + | [view][] | `ActionView::TestCase` | + | [routing][] | | + | [helper][] | `ActionView::TestCase` | + | [request][] | [`ActionDispatch::IntegrationTest`][] | + | [feature][] | | + | [system][] | [`ActionDispatch::SystemTestCase`][] | -The code above assigns `Widget.new` to the `@widget` variable in the view, and -then renders the view. +Follow the links above to see examples of each spec type, +or for official Rails API documentation on the given `TestCase` class. -Note that because view specs mix in `ActionView::TestCase` behavior, any -instance variables you set will be transparently propagated into your views -(similar to how instance variables you set in controller actions are made -available in views). For example: +> **Note: This is not a checklist.** +> +> Ask a hundred developers how to test an application, +> and you’ll get a hundred different answers. +> +> RSpec Rails provides thoughtfully selected features +> to encourage good testing practices, but there’s no “right” way to do it. +> Ultimately, it’s up to you to decide how your test suite will be composed. + +When creating a spec file, +assign it a type in the top-level `describe` block, like so: ```ruby -@widget = Widget.new -render # @widget is available inside the view -``` - -RSpec doesn't officially support this pattern, which only works as a -side-effect of the inclusion of `ActionView::TestCase`. Be aware that it may be -made unavailable in the future. - -#### Upgrade note - -```ruby -# rspec-rails-1.x -assigns[key] = value - -# rspec-rails-2.x+ -assign(key, value) -``` +# spec/models/user_spec.rb -### `rendered` - -This represents the rendered view. - -```ruby -render -expect(rendered).to match /Some text expected to appear on the page/ +RSpec.describe User, type: :model do +... ``` -#### Upgrade note +[request]: https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec +[feature]: https://www.relishapp.com/rspec/rspec-rails/docs/feature-specs/feature-spec +[system]: https://relishapp.com/rspec/rspec-rails/docs/system-specs/system-spec +[model]: https://www.relishapp.com/rspec/rspec-rails/docs/model-specs +[controller]: https://www.relishapp.com/rspec/rspec-rails/docs/controller-specs +[mailer]: https://relishapp.com/rspec/rspec-rails/docs/mailer-specs +[job]: https://relishapp.com/rspec/rspec-rails/docs/job-specs/job-spec +[view]: https://www.relishapp.com/rspec/rspec-rails/docs/view-specs/view-spec +[routing]: https://www.relishapp.com/rspec/rspec-rails/docs/routing-specs +[helper]: https://www.relishapp.com/rspec/rspec-rails/docs/helper-specs/helper-spec +[`ActionDispatch::IntegrationTest`]: https://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html +[`ActionDispatch::SystemTestCase`]: https://api.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html +[`ActionController::TestCase`]: https://api.rubyonrails.org/classes/ActionController/TestCase.html +[in the appropriate folder]: https://relishapp.com/rspec/rspec-rails/docs/directory-structure -```ruby -# rspec-rails-1.x -render -response.should xxx +### System specs, feature specs, request specs–what’s the difference? -# rspec-rails-2.x+ -render -rendered.should xxx +RSpec Rails provides some end-to-end (entire application) testing capability +to specify the interaction with the client. -# rspec-rails-2.x+ with expect syntax -render -expect(rendered).to xxx -``` +#### System specs -## Routing specs +Also called **acceptance tests**, **browser tests**, or **end-to-end tests**, +system specs test the application from the perspective of a _human client._ +The test code walks through a user’s browser interactions, -Routing specs default to residing in the `spec/routing` folder. Tagging any -context with the metadata `:type => :routing` treats its examples as routing -specs. +* `visit '/login'` +* `fill_in 'Name', with: 'jdoe'` -```ruby -require 'rails_helper' - -RSpec.describe "routing to profiles", :type => :routing do - it "routes /profile/:username to profile#show for username" do - expect(:get => "/profiles/jsmith").to route_to( - :controller => "profiles", - :action => "show", - :username => "jsmith" - ) - end +and the expectations revolve around page content. - it "does not expose a list of profiles" do - expect(:get => "/profiles").not_to be_routable - end -end -``` +* `expect(page).to have_text('Welcome')` -### Upgrade note +Because system specs are a wrapper around Rails’ built-in `SystemTestCase`, +they’re only available on Rails 5.1+. +(Feature specs serve the same purpose, but without this dependency.) -`route_for` from rspec-rails-1.x is gone. Use `route_to` and `be_routable` -instead. +#### Feature specs -## Helper specs +Before Rails introduced system testing facilities, +feature specs were the only spec type for end-to-end testing. +While the RSpec team now [officially recommends system specs][] instead, +feature specs are still fully supported, look basically identical, +and work on older versions of Rails. -Helper specs default to residing in the `spec/helpers` folder. Tagging any -context with the metadata `:type => :helper` treats its examples as helper -specs. +On the other hand, feature specs require non-trivial configuration +to get some important features working, +like JavaScript testing or making sure each test runs with a fresh DB state. +With system specs, this configuration is provided out-of-the-box. -Helper specs mix in ActionView::TestCase::Behavior. A `helper` object is -provided which mixes in the helper module being spec'd, along with -`ApplicationHelper` (if present). +Like system specs, feature specs require the [Capybara][] gem. +Rails 5.1+ includes it by default as part of system tests, +but if you don’t have the luxury of upgrading, +be sure to add it to the `:test` group of your `Gemfile` first: ```ruby -require 'rails_helper' - -RSpec.describe EventsHelper, :type => :helper do - describe "#link_to_event" do - it "displays the title, and formatted date" do - event = Event.new("Ruby Kaigi", Date.new(2010, 8, 27)) - # helper is an instance of ActionView::Base configured with the - # EventsHelper and all of Rails' built-in helpers - expect(helper.link_to_event).to match /Ruby Kaigi, 27 Aug, 2010/ - end - end +group :test do + gem "capybara" end ``` -## Matchers - -Several domain-specific matchers are provided to each of the example group -types. Most simply delegate to their equivalent Rails' assertions. - -### `be_a_new` - -- Available in all specs -- Primarily intended for controller specs - -```ruby -expect(object).to be_a_new(Widget) -``` - -Passes if the object is a `Widget` and returns true for `new_record?` +[officially recommends system specs]: https://rspec.info/blog/2017/10/rspec-3-7-has-been-released/#rails-actiondispatchsystemtest-integration-system-specs +[Capybara]: https://github.com/teamcapybara/capybara -### `render_template` +#### Request specs -- Delegates to Rails' `assert_template` -- Available in request, controller, and view specs +Request specs are for testing the application +from the perspective of a _machine client._ +They begin with an HTTP request and end with the HTTP response, +so they’re faster than feature specs, +but do not examine your app’s UI or JavaScript. -In request and controller specs, apply to the `response` object: +Request specs provide a high-level alternative to controller specs. +In fact, as of RSpec 3.5, both the Rails and RSpec teams +[discourage directly testing controllers][] +in favor of functional tests like request specs. -```ruby -expect(response).to render_template("new") -``` - -In view specs, apply to the `view` object: - -```ruby -expect(view).to render_template(:partial => "_form", :locals => { :widget => widget } ) -``` - -### `redirect_to` - -- Delegates to `assert_redirect` -- Available in request and controller specs - -```ruby -expect(response).to redirect_to(widgets_path) -``` - -### `route_to` - -- Delegates to Rails' `assert_routing` -- Available in routing and controller specs - -```ruby -expect(:get => "/widgets").to route_to(:controller => "widgets", :action => "index") -``` - -### `be_routable` - -Passes if the path is recognized by Rails' routing. This is primarily intended -to be used with `not_to` to specify standard CRUD routes which should not be -routable. - -```ruby -expect(:get => "/widgets/1/edit").not_to be_routable -``` - -### `have_http_status` - -- Passes if `response` has a matching HTTP status code -- The following symbolic status codes are allowed: - - `Rack::Utils::SYMBOL_TO_STATUS_CODE` - - One of the defined `ActionDispatch::TestResponse` aliases: - - `:error` - - `:missing` - - `:redirect` - - `:success` -- Available in controller, feature, and request specs. - -In controller and request specs, apply to the `response` object: +When writing them, try to answer the question, +“For a given HTTP request (verb + path + parameters), +what HTTP response should the application return?” -```ruby -expect(response).to have_http_status(201) -expect(response).not_to have_http_status(:created) -``` - -In feature specs, apply to the `page` object: +[discourage directly testing controllers]: https://rspec.info/blog/2016/07/rspec-3-5-has-been-released/#rails-support-for-rails-5 -```ruby -expect(page).to have_http_status(:success) -``` - -## `rake` tasks - -Several rake tasks are provided as a convenience for working with RSpec. To run -the entire spec suite use `rake spec`. To run a subset of specs use the -associated type task, for example `rake spec:models`. - -A full list of the available rake tasks can be seen by running `rake -T | grep -spec`. +## Contributing -### Customizing `rake` tasks +- [Build details](BUILD_DETAIL.md) +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Detailed contributing guide](CONTRIBUTING.md) -If you want to customize the behavior of `rake spec`, you may [define your own -task in the `Rakefile` for your -project](https://www.relishapp.com/rspec/rspec-core/docs/command-line/rake-task). -However, you must first clear the task that rspec-rails defined: +Once you’ve cloned the repo and [set up the environment](DEVELOPMENT.md), +you can run the specs and Cucumber features, or submit a pull request. -```ruby -task("spec").clear -``` +## See Also +### RSpec base libraries -## Also see +* +* +* +* -* [https://github.com/rspec/rspec](https://github.com/rspec/rspec) -* [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core) -* [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations) -* [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks) +### Recommended third-party extensions -## Feature Requests & Bugs +* [FactoryBot](https://github.com/thoughtbot/factory_bot) +* [Capybara](https://github.com/jnicklas/capybara) + (Included by default in Rails 5.1+. + Note that [additional configuration is required][] to use the Capybara DSL + anywhere other than system specs and feature specs.) -See + [additional configuration is required]: https://rubydoc.info/gems/rspec-rails/file/Capybara.md diff --git a/Rakefile b/Rakefile index 6bb2e0c4cd..de4c5b3492 100644 --- a/Rakefile +++ b/Rakefile @@ -183,7 +183,7 @@ namespace :no_active_record do # Rails 4 cannot use a `rails` binstub generated by Bundler sh "rm -f #{bindir}/rails" - sh "bundle exec rails new #{example_app_dir} --no-rc --skip-active-record --skip-javascript --skip-sprockets --skip-git --skip-test-unit --skip-listen --skip-bundle --template=example_app_generator/generate_app.rb" + sh "bundle exec rails new #{example_app_dir} --no-rc --skip-active-record --skip-javascript --skip-bootsnap --skip-sprockets --skip-git --skip-test-unit --skip-listen --skip-bundle --template=example_app_generator/generate_app.rb" in_example_app(:app_dir => example_app_dir) do sh "./travis_retry_bundle_install.sh 2>&1" diff --git a/appveyor.yml b/appveyor.yml index f234a3eeb0..b2fac867df 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -# This file was generated on 2019-01-03T20:34:23+00:00 from the rspec-dev repo. +# This file was generated on 2019-07-24T15:33:53+02:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. version: "{build}" @@ -38,3 +38,5 @@ environment: - ruby_version: 22 - ruby_version: 23-x64 - ruby_version: 24-x64 + - ruby_version: 25-x64 + - ruby_version: 26-x64 diff --git a/example_app_generator/generate_app.rb b/example_app_generator/generate_app.rb index 6841db7695..c9b3c4c315 100644 --- a/example_app_generator/generate_app.rb +++ b/example_app_generator/generate_app.rb @@ -23,6 +23,7 @@ gsub_file "Gemfile", /.*debugger.*/, '' gsub_file "Gemfile", /.*byebug.*/, "gem 'byebug', '~> 9.0.6'" gsub_file "Gemfile", /.*puma.*/, "" + gsub_file "Gemfile", /.*sqlite3.*/, "gem 'sqlite3', '~> 1.3.6'" if RUBY_VERSION < '2.2.2' gsub_file "Gemfile", /.*rdoc.*/, "gem 'rdoc', '< 6'" end @@ -31,7 +32,15 @@ append_to_file('Gemfile', "gem 'rails-controller-testing', :git => 'https://github.com/rails/rails-controller-testing'\n") end - if Rails::VERSION::STRING >= '5.2.0' + if Rails::VERSION::STRING >= "5.1.0" + if Rails::VERSION::STRING >= "5.2.0" && RUBY_VERSION < '2.3.0' + gsub_file "Gemfile", /.*chromedriver-helper.*/, "gem 'webdrivers', '< 4.0.0'" + else + gsub_file "Gemfile", /.*chromedriver-helper.*/, "gem 'webdrivers'" + end + end + + if Rails::VERSION::STRING >= '5.2.0' && Rails::VERSION::STRING < '6' copy_file sqlite_initializer, 'config/initializers/sqlite3_fix.rb' end diff --git a/features/Transactions.md b/features/Transactions.md index 7594f7ea44..634462eba5 100644 --- a/features/Transactions.md +++ b/features/Transactions.md @@ -23,15 +23,15 @@ simply tell RSpec to tell Rails not to manage transactions: config.use_transactional_fixtures = false end -### Data created in `before(:each)` are rolled back +### Data created in `before(:example)` are rolled back -Any data you create in a `before(:each)` hook will be rolled back at the end of +Any data you create in a `before(:example)` hook will be rolled back at the end of the example. This is a good thing because it means that each example is isolated from state that would otherwise be left around by the examples that already ran. For example: describe Widget do - before(:each) do + before(:example) do @widget = Widget.create end @@ -48,34 +48,34 @@ The `@widget` is recreated in each of the two examples above, so each example has a different object, _and_ the underlying data is rolled back so the data backing the `@widget` in each example is new. -### Data created in `before(:all)` are _not_ rolled back +### Data created in `before(:context)` are _not_ rolled back -`before(:all)` hooks are invoked before the transaction is opened. You can use +`before(:context)` hooks are invoked before the transaction is opened. You can use this to speed things up by creating data once before any example in a group is run, however, this introduces a number of complications and you should only do this if you have a firm grasp of the implications. Here are a couple of guidelines: -1. Be sure to clean up any data in an `after(:all)` hook: +1. Be sure to clean up any data in an `after(:context)` hook: - before(:all) do + before(:context) do @widget = Widget.create! end - after(:all) do + after(:context) do @widget.destroy end If you don't do that, you'll leave data lying around that will eventually interfere with other examples. -2. Reload the object in a `before(:each)` hook. +2. Reload the object in a `before(:example)` hook. - before(:all) do + before(:context) do @widget = Widget.create! end - before(:each) do + before(:example) do @widget.reload end diff --git a/features/matchers/have_http_status_matcher.feature b/features/matchers/have_http_status_matcher.feature index 1b197a607c..38f6252924 100644 --- a/features/matchers/have_http_status_matcher.feature +++ b/features/matchers/have_http_status_matcher.feature @@ -1,12 +1,16 @@ Feature: `have_http_status` matcher The `have_http_status` matcher is used to specify that a response returns a - desired status code. + desired status code. It accepts one argument in any of the following formats: + + * numeric code + * status name as defined in `Rack::Utils::SYMBOL_TO_STATUS_CODE` + * generic status type (`:success`, `:missing`, `:redirect`, or `:error`) The matcher works on any `response` object. It is available for use in [controller specs](../controller-specs), [request specs](../request-specs), and [feature specs](../feature-specs). - Scenario: Checking a numeric response code + Scenario: Checking a numeric status code Given a file named "spec/controllers/application_controller_spec.rb" with: """ruby require "rails_helper" @@ -31,7 +35,7 @@ Feature: `have_http_status` matcher When I run `rspec spec` Then the examples should all pass - Scenario: Checking a symbolic response code + Scenario: Checking a symbolic status name Given a file named "spec/controllers/application_controller_spec.rb" with: """ruby require "rails_helper" @@ -56,7 +60,7 @@ Feature: `have_http_status` matcher When I run `rspec spec` Then the examples should all pass - Scenario: Checking a general response code + Scenario: Checking a symbolic generic status type Given a file named "spec/controllers/application_controller_spec.rb" with: """ruby require "rails_helper" diff --git a/features/view_specs/view_spec.feature b/features/view_specs/view_spec.feature index e327816283..21a1af2c85 100644 --- a/features/view_specs/view_spec.feature +++ b/features/view_specs/view_spec.feature @@ -1,6 +1,26 @@ Feature: view spec - View specs live in spec/views and render view templates in isolation. + View specs are marked by `:type => :view` + or if you have set `config.infer_spec_type_from_file_location!` + by placing them in `spec/views`. + + Use them to test the content of view templates + without invoking a specific controller. + They generally follow three steps: + + ```ruby + assign(:widget, Widget.new) # sets @widget = Widget.new in the view template + + render + + expect(rendered).to match(/text/) + ``` + + 1. Use the `assign` method to set instance variables in the view. + + 2. Use the `render` method to render the view. + + 3. Set expectations against the resulting rendered template. Scenario: View specs render the described view file Given a file named "spec/views/widgets/index.html.erb_spec.rb" with: @@ -73,6 +93,34 @@ Feature: view spec When I run `rspec spec/views` Then the examples should all pass + Scenario: View specs can render templates in layouts + Given a file named "spec/views/widgets/widget.html.erb_spec.rb" with: + """ruby + require "rails_helper" + + RSpec.describe "rendering the widget template" do + context "with the inventory layout" do + it "displays the widget" do + assign(:widget, Widget.create!(:name => "slicer")) + + render :template => "widgets/widget.html.erb", :layout => "layouts/inventory" + + expect(rendered).to match /slicer/ + end + end + end + """ + And a file named "app/views/widgets/widget.html.erb" with: + """ +

<%= @widget.name %>

+ """ + And a file named "app/views/layouts/inventory.html.erb" with: + """ + <%= yield %> + """ + When I run `rspec spec/views` + Then the examples should all pass + Scenario: View specs can have description that includes the format and handler Given a file named "spec/views/widgets/widget.xml.erb_spec.rb" with: """ruby diff --git a/lib/generators/rspec/model/model_generator.rb b/lib/generators/rspec/model/model_generator.rb index 6c7db59bd4..789fcda83c 100644 --- a/lib/generators/rspec/model/model_generator.rb +++ b/lib/generators/rspec/model/model_generator.rb @@ -23,7 +23,7 @@ def create_model_spec def create_fixture_file return unless missing_fixture_replacement? - template 'fixtures.yml', File.join('spec/fixtures', "#{table_name}.yml") + template 'fixtures.yml', File.join('spec/fixtures', class_path, "#{(pluralize_table_names? ? plural_file_name : file_name)}.yml") end private diff --git a/lib/rspec/rails/matchers/be_valid.rb b/lib/rspec/rails/matchers/be_valid.rb index ec40d33452..7f23dbb04e 100644 --- a/lib/rspec/rails/matchers/be_valid.rb +++ b/lib/rspec/rails/matchers/be_valid.rb @@ -15,7 +15,7 @@ def matches?(actual) def failure_message message = "expected #{actual.inspect} to be valid" - if actual.respond_to?(:errors) + if actual.respond_to?(:errors) && actual.method(:errors).arity < 1 errors = if actual.errors.respond_to?(:full_messages) actual.errors.full_messages else diff --git a/lib/rspec/rails/version.rb b/lib/rspec/rails/version.rb index 18c1695dd5..1fe4a171d2 100644 --- a/lib/rspec/rails/version.rb +++ b/lib/rspec/rails/version.rb @@ -3,7 +3,7 @@ module Rails # Version information for RSpec Rails. module Version # Current version of RSpec Rails, in semantic versioning format. - STRING = '3.8.2' + STRING = '3.8.3' end end end diff --git a/rfcs/versioning-strategy.md b/rfcs/versioning-strategy.md new file mode 100644 index 0000000000..d757b03451 --- /dev/null +++ b/rfcs/versioning-strategy.md @@ -0,0 +1,118 @@ +# RFC: RSpec Rails new versioning strategy + +Hi Folks, + +This RFC captures a proposal for RSpec Rails' new versioning strategy. Specifically, this represents two things: +* A change in representation of what we use SemVer for, in RSpec Rails +* A departure from RSpec Rails having the same versioning as RSpec's other gems (-core, -mocks, -expectations, and, internally, -support). + +## Need + +Currently, the RSpec Rails [build matrix](https://travis-ci.org/rspec/rspec-rails) +has 63 entries. This permutes rubies since 1.8.7 and Rails versions since 3.0. +As of right now the full build takes over an hour to run, and this makes cycling +for PRs and quick iterative development very difficult. + +RSpec Rails's code itself also is damaged by this. Everything from the Gemfile +setup[1](https://github.com/rspec/rspec-rails/blob/1d2935861c89246236b46f77de753cda5ea67d61/Gemfile) +[2](https://github.com/rspec/rspec-rails/blob/1d2935861c89246236b46f77de753cda5ea67d61/Gemfile-rails-dependencies) +[3](https://github.com/rspec/rspec-rails/blob/1d2935861c89246236b46f77de753cda5ea67d61/Gemfile-rspec-dependencies), +to the [library code](https://github.com/rspec/rspec-rails/blob/1d2935861c89246236b46f77de753cda5ea67d61/lib/rspec/rails/configuration.rb#L128-L143), +to the [test setup](https://github.com/rspec/rspec-rails/blob/1d2935861c89246236b46f77de753cda5ea67d61/Rakefile#L29-L53), +to the [tests themselves](https://github.com/rspec/rspec-rails/blob/1d2935861c89246236b46f77de753cda5ea67d61/spec/rspec/rails/example/job_example_group_spec.rb) +contain conditional execution based on which Rails version is currently loaded. +This makes ongoing maintenance difficult, as it requires that RSpec Rails' +maintainers be conscious of every Rails version that might be loaded. +Acknowledging that, patches still sometimes break people's suites, due to the +number of permutations of gems that can be loaded. + +Our need is therefore best characterised by cost of maintenance. Having to +maintain several versions of Rails and Ruby costs us a lot. It makes our +development slower, and forces us to write against Rails versions that most +people no longer use. I want RSpec Rails development to be fast, and +lightweight, much like it was when I joined the RSpec project. + +## Approach + +The approach to fix this is an adjustment of versioning strategy. RSpec Rails +will still continue to maintain a SemVer strategy, that is: + +* Breaking changes in Majors +* New features in Minors +* Bug Fixes in Patchlevels + +For the purposes of this versioning strategy, we consider removing a Rails or +Ruby version to be a breaking change. We consider adding a Ruby or Rails version +to be a Minor, along with normal feature releases. + +The intent, however, is to change the cycle of these releases to align with +Rails. Specifically, a Rails release cycle typically looks like: + +* Release a Major X.0, (X-2).0 is no longer supported, all but the most recent + (X--1) series are unsupported, introduces deprecation warnings for many + features +* Release a Minor X.1, deprecation warnings from X.0 are now errors +* Release a Minor X.2, new features are added, some further deprecation warnings + from X.1 may now be broken. + +As such, RSpec Rails's new versioning strategy will be: + +* Release a major with any Rails Major, removing support for any newly + unsupported versions of Rails, and adding support for the new major. +* Release a minor with any Rails Minor, adding support for the new features + * Additionally, release minors for any new RSpec features +* Release patchlevels frequently, as bugfixes occur. + +As to the transition to this strategy: it is my intent to move to this strategy +along with releasing support for Rails 5.2 for RSpec Rails, so relatively soon, +dropping support for anything below Rails 4.2, and any Rubies below 2.2. This +means that RSpec Rails 4.0.0 will be released within a handful of months from +this RFC being accepted. + +I do expect this to mean that the major version of RSpec Rails will increment +relatively quickly with comparison to the rest of RSpec. I do not think that is +necessarily a bad thing, it does not mean that the library is unstable as such, +but rather that we are tracking our dependencies accurately. + +Traditionally, RSpec Rails has been versioned at the same version number as +RSpec. This will represent a departure from that. In order to maintain +compatibility, RSpec Rails will continue to support the RSpec 3 series, and will +probably add support for RSpec 4 without breaking changes. To do this, I intend +to move off RSpec Rails using any private APIs from RSpec. + +## Benefits + +Execution of this strategy will greatly increase our ability to maintain RSpec, +and release against modern Rails versions. While RSpec has been very stable and +essentially continuously expanded Rails version support for the last few years, +this has now become unsustainable and we want to take this tradeoff to best +serve the needs of the community. + +## Competition + +### Do nothing + +If we do this, it will become deeply unsustainable for us to maintain RSpec +Rails in the future. We have too many Rails versions today, and we expect the +rate of Rails releases to increase as time goes on. As such, it is our intent to +start dropping Rails versions inline with Rails, in order to continue to improve +the RSpec Rails gem. + +### Keep inline versioning with RSpec + +RSpec has a materially different versioning need to Rails. Specifically, RSpec +as of today is a mostly entirely stable library. It's development needs are +largely as feature proposals occur, or as bugs are found. RSpec has no external +forces pushing versioning pressure on it (other than say, ruby and bundler, +which are themselves, relatively stable). This is not true of RSpec Rails, which +is popular, and there is a general expectation that RSpec will work with Rails +applications, as of right now, we typically lag behind a Rails release by weeks +when a Rails version gets released. + +## Conclusions + +It is my intent to ship this change relatively soon, inline with Rails 5.2 +support in the next few months. I really do think this represents the best +future tradeoff for RSpec. If you strongly believe that dropping support for +Rails versions lower than 4.2 will affect your needs, please do let us know so +that we can consider the full weight of your use case. diff --git a/script/clone_all_rspec_repos b/script/clone_all_rspec_repos index dcd4d914da..70b9e0a4e7 100755 --- a/script/clone_all_rspec_repos +++ b/script/clone_all_rspec_repos @@ -1,5 +1,5 @@ #!/bin/bash -# This file was generated on 2019-01-03T20:34:23+00:00 from the rspec-dev repo. +# This file was generated on 2019-07-24T15:33:53+02:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. set -e diff --git a/script/downgrade_bundler_on_old_rails b/script/downgrade_bundler_on_old_rails index 36e54feea2..c86d3762f1 100755 --- a/script/downgrade_bundler_on_old_rails +++ b/script/downgrade_bundler_on_old_rails @@ -5,13 +5,14 @@ set -e source script/functions.sh -if ruby -e "exit(ENV['RAILS_VERSION'].to_f < 5)"; then +if ruby -e "exit(ENV['RAILS_VERSION'].scan(/\d+\.\d+.\d+/)[0].to_f < 5)"; then # On Rails versions less than 5, Bundler 2.0 is not supported echo "Warning dowgrading to older version of Bundler" - gem uninstall -aIx bundler + + gem uninstall -aIx bundler || echo "Warning error occured removing bundler via gem" # this only works on Ruby 2.3 which is luckily the version we need to fix - if is_ruby_23_plus; then + if is_ruby_23; then rvm @global do gem uninstall -aIx bundler fi diff --git a/script/functions.sh b/script/functions.sh index 12c1d35904..32b2d66a8f 100644 --- a/script/functions.sh +++ b/script/functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2019-01-03T20:34:23+00:00 from the rspec-dev repo. +# This file was generated on 2019-07-24T15:33:53+02:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -19,7 +19,7 @@ fi function clone_repo { if [ ! -d $1 ]; then # don't clone if the dir is already there - travis_retry eval "git clone git://github.com/rspec/$1 --depth 1 --branch $MAINTENANCE_BRANCH" + travis_retry eval "git clone https://github.com/rspec/$1 --depth 1 --branch $MAINTENANCE_BRANCH" fi; } @@ -72,7 +72,7 @@ function run_specs_one_by_one { for file in `find spec -iname '*_spec.rb'`; do echo "Running $file" - bin/rspec $file -b --format progress + bin/rspec $file --format progress done } diff --git a/script/predicate_functions.sh b/script/predicate_functions.sh index cfdc471f79..e2f4ac24cc 100644 --- a/script/predicate_functions.sh +++ b/script/predicate_functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2019-01-03T20:34:23+00:00 from the rspec-dev repo. +# This file was generated on 2019-07-24T15:33:53+02:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. function is_mri { @@ -33,6 +33,18 @@ function is_mri_192 { fi } +function is_not_mri_193 { + if is_mri; then + if ruby -e "exit(RUBY_VERSION == '1.9.3')"; then + return 1 + else + return 0 + fi + else + return 0 + fi +} + function is_mri_192_plus { if is_mri; then if ruby -e "exit(RUBY_VERSION.to_f > 1.8)"; then @@ -57,6 +69,14 @@ function is_mri_2plus { fi } +function is_ruby_23 { + if ruby -e "exit(RUBY_VERSION.to_f == 2.3)"; then + return 0 + else + return 1 + fi +} + function is_ruby_23_plus { if ruby -e "exit(RUBY_VERSION.to_f >= 2.3)"; then return 0 diff --git a/script/run_build b/script/run_build index 891a05e966..a4a5c3dc01 100755 --- a/script/run_build +++ b/script/run_build @@ -1,5 +1,5 @@ #!/bin/bash -# This file was generated on 2019-01-03T20:34:23+00:00 from the rspec-dev repo. +# This file was generated on 2019-07-24T15:33:53+02:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. set -e @@ -29,7 +29,13 @@ if style_and_lint_enforced; then fi if is_mri; then - fold "one-by-one specs" run_specs_one_by_one + # As of 2019-09-24 this causes a huge log due to a cicular dependency + # disable for now + if is_not_mri_193; then + fold "one-by-one specs" run_specs_one_by_one + else + echo "Skipping one-by-one specs due to issue on 1.9.3" + fi run_all_spec_suites else echo "Skipping the rest of the build on non-MRI rubies" diff --git a/script/travis_functions.sh b/script/travis_functions.sh index 8b89cc8bdf..15cc10279f 100644 --- a/script/travis_functions.sh +++ b/script/travis_functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2019-01-03T20:34:23+00:00 from the rspec-dev repo. +# This file was generated on 2019-07-24T15:33:53+02:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. # Taken from: diff --git a/script/update_rubygems_and_install_bundler b/script/update_rubygems_and_install_bundler index bcf7e86db6..8dbe307dd5 100755 --- a/script/update_rubygems_and_install_bundler +++ b/script/update_rubygems_and_install_bundler @@ -1,5 +1,5 @@ #!/bin/bash -# This file was generated on 2019-01-03T20:34:23+00:00 from the rspec-dev repo. +# This file was generated on 2019-07-24T15:33:53+02:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. set -e diff --git a/spec/generators/rspec/model/model_generator_spec.rb b/spec/generators/rspec/model/model_generator_spec.rb index dee5c99e2f..aabf05b83e 100644 --- a/spec/generators/rspec/model/model_generator_spec.rb +++ b/spec/generators/rspec/model/model_generator_spec.rb @@ -12,27 +12,10 @@ gen.invoke_all end - describe 'the generated files' do - describe 'with fixtures' do - before do - run_generator %w(posts --fixture) - end - - describe 'the spec' do - subject { file('spec/models/posts_spec.rb') } - - it { is_expected.to exist } - it { is_expected.to contain(/require 'rails_helper'/) } - it { is_expected.to contain(/^RSpec.describe Posts, #{type_metatag(:model)}/) } - end - - describe 'the fixtures' do - subject { file('spec/fixtures/posts.yml') } - - it { is_expected.to contain(Regexp.new('# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html')) } - end - end + it_behaves_like 'a model generator with fixtures', 'admin/posts', 'Admin::Posts' + it_behaves_like 'a model generator with fixtures', 'posts', 'Posts' + describe 'the generated files' do describe 'without fixtures' do before do run_generator %w(posts) diff --git a/spec/rspec/rails/matchers/be_valid_spec.rb b/spec/rspec/rails/matchers/be_valid_spec.rb index 8ce9bb1ebc..ca4e7ecc77 100644 --- a/spec/rspec/rails/matchers/be_valid_spec.rb +++ b/spec/rspec/rails/matchers/be_valid_spec.rb @@ -24,9 +24,19 @@ def valid? end end + class Car + def valid? + false + end + + def errors(_) + end + end + let(:post) { Post.new } let(:book) { Book.new } let(:boat) { Boat.new } + let(:car) { Car.new } it "includes the error messages in the failure message" do expect { @@ -46,6 +56,12 @@ def valid? }.to raise_exception(/expected .+ to be valid\z/) end + it "includes a brief error message when error message is wrong arity" do + expect { + expect(car).to be_valid + }.to raise_exception(/expected .+ to be valid\z/) + end + it "includes a failure message for the negative case" do allow(post).to receive(:valid?) { true } expect { diff --git a/spec/support/generators.rb b/spec/support/generators.rb index d380353b1b..9f38e14132 100644 --- a/spec/support/generators.rb +++ b/spec/support/generators.rb @@ -19,6 +19,24 @@ def self.included(klass) klass.extend(Macros) end + shared_examples_for 'a model generator with fixtures' do |name, class_name| + before { run_generator [name, '--fixture'] } + + describe 'the spec' do + subject { file("spec/models/#{name}_spec.rb") } + + it { is_expected.to exist } + it { is_expected.to contain(/require 'rails_helper'/) } + it { is_expected.to contain(/^RSpec.describe #{class_name}, #{type_metatag(:model)}/) } + end + + describe 'the fixtures' do + subject { file("spec/fixtures/#{name}.yml") } + + it { is_expected.to contain(Regexp.new('# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html')) } + end + end + shared_examples_for "a request spec generator" do describe 'generated with flag `--no-request-specs`' do before do