├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── README.md ├── Rakefile ├── cucumber.yml ├── devise-specs.gemspec ├── features ├── .nav ├── delete_specs.feature ├── generate_specs_with_fabricators.feature ├── generate_specs_with_factories.feature ├── readme.md ├── skip_specs.feature ├── specs_pass.feature ├── step_definitions │ ├── devise-specs_steps.rb │ └── rspec_steps.rb └── support │ └── env.rb ├── fixtures ├── action_mailer.rb ├── application.html.erb ├── index.html.erb └── routes.rb └── lib ├── devise-specs.rb ├── devise └── specs │ └── railtie.rb └── generators └── devise ├── specs_generator.rb └── templates ├── devise.rb.tt ├── factory_bot.rb.tt ├── resource_resets_password_spec.rb.tt ├── resource_signs_in_spec.rb.tt ├── resource_signs_out_spec.rb.tt └── resource_signs_up_spec.rb.tt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | ruby-version: ['2.6', '2.7'] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Ruby 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby-version }} 23 | - name: Bundle install 24 | run: bundle install 25 | - name: Run tests 26 | run: bundle exec rake 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | tmp/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.0.5 2 | 3 | * Support latest Factory Bot and RSpec Rails versions 4 | 5 | ### 0.0.4 6 | 7 | * Make internal tests pass on Rails 5.1 and clean up docs 8 | 9 | 10 | ### 0.0.3 11 | 12 | * Use the new `Devise::Test::IntegrationHelpers` instead of the custom `sign_in` helper 13 | 14 | ### 0.0.2 15 | 16 | * Add more thorough expectations to generated specs 17 | * Rename specs:devise generator to devise:specs 18 | * Update README with Output and Testing sections 19 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # devise-specs [![Build Status][ci-image]][ci] [![Maintainability][grade-image]][grade] 2 | 3 | devise-specs is a Rails generator that adds the Devise authentication acceptance tests when you run the `devise` generator. The tests are RSpec feature specs containing Factory Bot or Fabrication fixture replacement methods and Capybara actions. 4 | 5 | ![Demo GIF](https://user-images.githubusercontent.com/49207/126929193-f9228814-6ff1-4e5b-b24e-762dbdee996f.gif) 6 | 7 | Generated feature specs test the following features: 8 | * Registration 9 | * Login 10 | * Logout 11 | * Password reset 12 | 13 | ## Installation 14 | 15 | Make sure `devise`, `devise-specs`, `rspec-rails`, `capybara` and fixture replacement gems are added to the `Gemfile`: 16 | ```ruby 17 | gem 'devise' 18 | 19 | group :development do 20 | gem 'devise-specs' 21 | end 22 | 23 | group :test do 24 | gem 'capybara' 25 | end 26 | 27 | group :development, :test do 28 | gem 'rspec-rails' 29 | gem 'factory_bot_rails' # or: gem 'fabrication' 30 | end 31 | ``` 32 | 33 | and then run `bundle install`. 34 | 35 | ## Setup 36 | 37 | Generate the RSpec configuratoin files: 38 | ``` 39 | $ bin/rails generate rspec:install 40 | ``` 41 | 42 | Generate the Devise configuration files and follow the setup instructions to define the default url options, root route and flash messages: 43 | ``` 44 | $ bin/rails generate devise:install 45 | ``` 46 | 47 | Configure the Action Mailer URL options for the test environment using the following line in `config/environments/test.rb`: 48 | ```ruby 49 | config.action_mailer.default_url_options = { host: 'localhost', port: 3001 } 50 | ``` 51 | 52 | Add the authentication links to the layout, `user_signed_in?` should be `admin_signed_in?` if your Devise model is `Admin`: 53 | ```erb 54 | <% if user_signed_in? %> 55 | <%= link_to 'Sign Out', destroy_user_session_path, method: :delete %> 56 | <% else %> 57 | <%= link_to 'Sign In', new_user_session_path %> 58 | <%= link_to 'Sign Up', new_user_registration_path %> 59 | <% end %> 60 | ``` 61 | 62 | ## Usage 63 | 64 | Specs are created automatically when you generate a Devise model, e.g. `User`: 65 | ``` 66 | $ bin/rails generate devise User 67 | ... 68 | invoke specs 69 | gsub spec/rails_helper.rb 70 | insert spec/factories/users.rb 71 | create spec/support/factory_bot.rb 72 | create spec/support/devise.rb 73 | create spec/features/user_signs_up_spec.rb 74 | create spec/features/user_signs_in_spec.rb 75 | create spec/features/user_signs_out_spec.rb 76 | create spec/features/user_resets_password_spec.rb 77 | ``` 78 | 79 | If a Devise model is already present, run the `devise:specs` generator directly: 80 | ``` 81 | $ bin/rails generate devise:specs User 82 | ``` 83 | 84 | Run the migrations: 85 | ``` 86 | $ bin/rails db:migrate RAILS_ENV=test 87 | ``` 88 | 89 | Make sure the specs pass: 90 | ``` 91 | $ rspec spec/features 92 | ......... 93 | 94 | Finished in 1.08 seconds (files took 2.1 seconds to load) 95 | 9 examples, 0 failures 96 | ``` 97 | 98 | ## Documentation 99 | 100 | Visit the [Relish docs](https://relishapp.com/andrii/devise-specs/docs) for all the available features and examples of the generated feature specs. 101 | 102 | ## Output 103 | 104 | `gsub spec/rails_helper.rb` 105 | 106 | Uncomments the line that auto-requires all files in the support directory. 107 | 108 | `insert spec/fabricators/*_fabricator.rb` 109 | 110 | Adds `email` and `password` attributes to the fabricator. 111 | 112 | `insert spec/factories/*.rb` 113 | 114 | Adds `email` and `password` attributes to the factory. 115 | 116 | `create spec/support/factory_bot.rb` 117 | 118 | Includes `FactoryBot::Syntax::Methods` into RSpec config to avoid prefacing Factory Bot methods with `FactoryBot`. 119 | 120 | `create spec/support/devise.rb` 121 | 122 | Includes Devise integration test helpers into feature specs. 123 | 124 | `create spec/features/*_spec.rb` 125 | 126 | Generates a corresponding feature spec. 127 | 128 | ## Testing 129 | 130 | Install system and development dependencies and run the tests: 131 | ``` 132 | $ bundle exec rake 133 | ``` 134 | 135 | ## License 136 | 137 | MIT License 138 | 139 | [ci-image]: https://github.com/ponosoft/devise-specs/actions/workflows/build.yml/badge.svg 140 | [ci]: https://github.com/ponosoft/devise-specs/actions/workflows/build.yml 141 | [grade-image]: https://api.codeclimate.com/v1/badges/b7e541f2f5171790638f/maintainability 142 | [grade]: https://codeclimate.com/github/ponosoft/devise-specs/maintainability 143 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'cucumber/rake/task' 2 | 3 | Cucumber::Rake::Task.new 4 | 5 | task default: :cucumber 6 | -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | default: --publish-quiet 2 | -------------------------------------------------------------------------------- /devise-specs.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'devise-specs' 3 | s.version = '0.0.5' 4 | s.authors = ['Andrii Ponomarov'] 5 | s.email = 'andrii.ponomarov@gmail.com' 6 | s.summary = 'Generates the Devise acceptance tests.' 7 | s.homepage = 'https://github.com/ponosoft/devise-specs' 8 | s.license = 'MIT' 9 | s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|features|fixtures)/}) } 10 | 11 | s.required_ruby_version = '>= 2.5.0' 12 | 13 | s.add_runtime_dependency 'devise', '~> 4.8' 14 | 15 | s.add_development_dependency 'aruba', '~> 1.1', '>= 1.1.2' 16 | s.add_development_dependency 'rake', '~> 13.0', '>= 13.0.6' 17 | end 18 | -------------------------------------------------------------------------------- /features/.nav: -------------------------------------------------------------------------------- 1 | - generate_specs_with_factories.feature 2 | - generate_specs_with_fabricators.feature 3 | - specs_pass.feature 4 | - delete_specs.feature 5 | - skip_specs.feature 6 | -------------------------------------------------------------------------------- /features/delete_specs.feature: -------------------------------------------------------------------------------- 1 | Feature: Delete specs 2 | 3 | Background: 4 | Given I set up devise-specs 5 | 6 | Scenario: undo changes with Factory Bot installed 7 | Given I install factory_bot_rails 8 | When I run `bin/rails destroy devise User` 9 | Then the output should contain: 10 | """ 11 | invoke specs 12 | remove spec/support/factory_bot.rb 13 | remove spec/support/devise.rb 14 | remove spec/features/user_signs_up_spec.rb 15 | remove spec/features/user_signs_in_spec.rb 16 | remove spec/features/user_signs_out_spec.rb 17 | remove spec/features/user_resets_password_spec.rb 18 | """ 19 | 20 | Scenario: undo changes with Fabrication installed 21 | Given I install fabrication 22 | When I run `bin/rails destroy devise User` 23 | Then the output should contain: 24 | """ 25 | invoke specs 26 | remove spec/support/devise.rb 27 | remove spec/features/user_signs_up_spec.rb 28 | remove spec/features/user_signs_in_spec.rb 29 | remove spec/features/user_signs_out_spec.rb 30 | remove spec/features/user_resets_password_spec.rb 31 | """ 32 | -------------------------------------------------------------------------------- /features/generate_specs_with_fabricators.feature: -------------------------------------------------------------------------------- 1 | Feature: Generate specs with fabricators 2 | 3 | Background: 4 | Given I set up devise-specs 5 | 6 | Scenario: running a devise generator with Fabrication installed 7 | Given I install fabrication 8 | When I run `bin/rails generate devise Admin` 9 | Then the output should contain: 10 | """ 11 | invoke specs 12 | gsub spec/rails_helper.rb 13 | insert spec/fabricators/admin_fabricator.rb 14 | create spec/support/devise.rb 15 | create spec/features/admin_signs_up_spec.rb 16 | create spec/features/admin_signs_in_spec.rb 17 | create spec/features/admin_signs_out_spec.rb 18 | create spec/features/admin_resets_password_spec.rb 19 | """ 20 | And the file "spec/features/admin_signs_up_spec.rb" should contain: 21 | """ruby 22 | require 'rails_helper' 23 | 24 | RSpec.feature 'Admin signs up' do 25 | scenario 'with valid data' do 26 | visit new_admin_registration_path 27 | 28 | fill_in 'Email', with: 'username@example.com' 29 | fill_in 'Password', with: 'password' 30 | fill_in 'Password confirmation', with: 'password' 31 | click_button 'Sign up' 32 | 33 | expect(page).to have_text 'Welcome! You have signed up successfully.' 34 | expect(page).to have_link 'Sign Out' 35 | expect(page).to have_current_path root_path 36 | end 37 | 38 | scenario 'with invalid data' do 39 | visit new_admin_registration_path 40 | 41 | click_button 'Sign up' 42 | 43 | expect(page).to have_text "Email can't be blank" 44 | expect(page).to have_text "Password can't be blank" 45 | expect(page).to have_no_link 'Sign Out' 46 | end 47 | end 48 | """ 49 | And the file "spec/features/admin_signs_in_spec.rb" should contain: 50 | """ruby 51 | require 'rails_helper' 52 | 53 | RSpec.feature 'Admin signs in' do 54 | scenario 'with valid credentials' do 55 | admin = Fabricate :admin 56 | 57 | visit new_admin_session_path 58 | 59 | fill_in 'Email', with: admin.email 60 | fill_in 'Password', with: admin.password 61 | click_button 'Log in' 62 | 63 | expect(page).to have_text 'Signed in successfully.' 64 | expect(page).to have_link 'Sign Out' 65 | expect(page).to have_current_path root_path 66 | end 67 | 68 | scenario 'with invalid credentials' do 69 | admin = Fabricate.build :admin 70 | 71 | visit new_admin_session_path 72 | 73 | fill_in 'Email', with: admin.email 74 | fill_in 'Password', with: admin.password 75 | click_button 'Log in' 76 | 77 | expect(page).to have_text 'Invalid Email or password.' 78 | expect(page).to have_no_link 'Sign Out' 79 | end 80 | end 81 | """ 82 | And the file "spec/features/admin_signs_out_spec.rb" should contain: 83 | """ruby 84 | require 'rails_helper' 85 | 86 | RSpec.feature 'Admin signs out' do 87 | scenario 'admin signed in' do 88 | admin = Fabricate :admin 89 | 90 | sign_in admin 91 | 92 | visit root_path 93 | 94 | click_link 'Sign Out' 95 | 96 | expect(page).to have_text 'Signed out successfully.' 97 | expect(page).to have_no_link 'Sign Out' 98 | expect(page).to have_current_path root_path 99 | end 100 | end 101 | """ 102 | And the file "spec/features/admin_resets_password_spec.rb" should contain: 103 | """ruby 104 | require 'rails_helper' 105 | 106 | RSpec.feature 'Admin resets a password' do 107 | scenario 'admin enters a valid email' do 108 | admin = Fabricate :admin 109 | 110 | visit new_admin_password_path 111 | 112 | fill_in 'Email', with: admin.email 113 | click_button 'Send me reset password instructions' 114 | 115 | expect(page).to have_text 'You will receive an email with instructions' 116 | expect(page).to have_current_path new_admin_session_path 117 | end 118 | 119 | scenario 'admin enters an invalid email' do 120 | visit new_admin_password_path 121 | 122 | fill_in 'Email', with: 'username@example.com' 123 | click_button 'Send me reset password instructions' 124 | 125 | expect(page).to have_text 'Email not found' 126 | end 127 | 128 | scenario 'admin changes password' do 129 | token = Fabricate(:admin).send_reset_password_instructions 130 | 131 | visit edit_admin_password_path(reset_password_token: token) 132 | 133 | fill_in 'New password', with: 'p4ssw0rd' 134 | fill_in 'Confirm new password', with: 'p4ssw0rd' 135 | click_button 'Change my password' 136 | 137 | expect(page).to have_text 'Your password has been changed successfully.' 138 | expect(page).to have_current_path root_path 139 | end 140 | 141 | scenario 'password reset token is invalid' do 142 | visit edit_admin_password_path(reset_password_token: 'token') 143 | 144 | fill_in 'New password', with: 'p4ssw0rd' 145 | fill_in 'Confirm new password', with: 'p4ssw0rd' 146 | click_button 'Change my password' 147 | 148 | expect(page).to have_text 'Reset password token is invalid' 149 | end 150 | end 151 | """ 152 | -------------------------------------------------------------------------------- /features/generate_specs_with_factories.feature: -------------------------------------------------------------------------------- 1 | Feature: Generate specs with factories 2 | 3 | Background: 4 | Given I set up devise-specs 5 | 6 | Scenario: running a devise generator with Factory Bot installed 7 | Given I install factory_bot_rails 8 | When I run `bin/rails generate devise User` 9 | Then the output should contain: 10 | """ 11 | invoke specs 12 | gsub spec/rails_helper.rb 13 | insert spec/factories/users.rb 14 | create spec/support/factory_bot.rb 15 | create spec/support/devise.rb 16 | create spec/features/user_signs_up_spec.rb 17 | create spec/features/user_signs_in_spec.rb 18 | create spec/features/user_signs_out_spec.rb 19 | create spec/features/user_resets_password_spec.rb 20 | """ 21 | And the file "spec/features/user_signs_up_spec.rb" should contain: 22 | """ruby 23 | require 'rails_helper' 24 | 25 | RSpec.feature 'User signs up' do 26 | scenario 'with valid data' do 27 | visit new_user_registration_path 28 | 29 | fill_in 'Email', with: 'username@example.com' 30 | fill_in 'Password', with: 'password' 31 | fill_in 'Password confirmation', with: 'password' 32 | click_button 'Sign up' 33 | 34 | expect(page).to have_text 'Welcome! You have signed up successfully.' 35 | expect(page).to have_link 'Sign Out' 36 | expect(page).to have_current_path root_path 37 | end 38 | 39 | scenario 'with invalid data' do 40 | visit new_user_registration_path 41 | 42 | click_button 'Sign up' 43 | 44 | expect(page).to have_text "Email can't be blank" 45 | expect(page).to have_text "Password can't be blank" 46 | expect(page).to have_no_link 'Sign Out' 47 | end 48 | end 49 | """ 50 | And the file "spec/features/user_signs_in_spec.rb" should contain: 51 | """ruby 52 | require 'rails_helper' 53 | 54 | RSpec.feature 'User signs in' do 55 | scenario 'with valid credentials' do 56 | user = create :user 57 | 58 | visit new_user_session_path 59 | 60 | fill_in 'Email', with: user.email 61 | fill_in 'Password', with: user.password 62 | click_button 'Log in' 63 | 64 | expect(page).to have_text 'Signed in successfully.' 65 | expect(page).to have_link 'Sign Out' 66 | expect(page).to have_current_path root_path 67 | end 68 | 69 | scenario 'with invalid credentials' do 70 | user = build :user 71 | 72 | visit new_user_session_path 73 | 74 | fill_in 'Email', with: user.email 75 | fill_in 'Password', with: user.password 76 | click_button 'Log in' 77 | 78 | expect(page).to have_text 'Invalid Email or password.' 79 | expect(page).to have_no_link 'Sign Out' 80 | end 81 | end 82 | """ 83 | And the file "spec/features/user_signs_out_spec.rb" should contain: 84 | """ruby 85 | require 'rails_helper' 86 | 87 | RSpec.feature 'User signs out' do 88 | scenario 'user signed in' do 89 | user = create :user 90 | 91 | sign_in user 92 | 93 | visit root_path 94 | 95 | click_link 'Sign Out' 96 | 97 | expect(page).to have_text 'Signed out successfully.' 98 | expect(page).to have_no_link 'Sign Out' 99 | expect(page).to have_current_path root_path 100 | end 101 | end 102 | """ 103 | And the file "spec/features/user_resets_password_spec.rb" should contain: 104 | """ruby 105 | require 'rails_helper' 106 | 107 | RSpec.feature 'User resets a password' do 108 | scenario 'user enters a valid email' do 109 | user = create :user 110 | 111 | visit new_user_password_path 112 | 113 | fill_in 'Email', with: user.email 114 | click_button 'Send me reset password instructions' 115 | 116 | expect(page).to have_text 'You will receive an email with instructions' 117 | expect(page).to have_current_path new_user_session_path 118 | end 119 | 120 | scenario 'user enters an invalid email' do 121 | visit new_user_password_path 122 | 123 | fill_in 'Email', with: 'username@example.com' 124 | click_button 'Send me reset password instructions' 125 | 126 | expect(page).to have_text 'Email not found' 127 | end 128 | 129 | scenario 'user changes password' do 130 | token = create(:user).send_reset_password_instructions 131 | 132 | visit edit_user_password_path(reset_password_token: token) 133 | 134 | fill_in 'New password', with: 'p4ssw0rd' 135 | fill_in 'Confirm new password', with: 'p4ssw0rd' 136 | click_button 'Change my password' 137 | 138 | expect(page).to have_text 'Your password has been changed successfully.' 139 | expect(page).to have_current_path root_path 140 | end 141 | 142 | scenario 'password reset token is invalid' do 143 | visit edit_user_password_path(reset_password_token: 'token') 144 | 145 | fill_in 'New password', with: 'p4ssw0rd' 146 | fill_in 'Confirm new password', with: 'p4ssw0rd' 147 | click_button 'Change my password' 148 | 149 | expect(page).to have_text 'Reset password token is invalid' 150 | end 151 | end 152 | """ 153 | -------------------------------------------------------------------------------- /features/readme.md: -------------------------------------------------------------------------------- 1 | devise-specs is a Rails generator that creates RSpec feature specs for the default Devise registration, login, logout and password reset functionality. Cucumber feature files below act as an executable documentation for the different use cases of the library. 2 | -------------------------------------------------------------------------------- /features/skip_specs.feature: -------------------------------------------------------------------------------- 1 | Feature: Skip specs 2 | 3 | Background: 4 | Given I set up devise-specs 5 | 6 | Scenario: running a devise generator with --skip-specs 7 | When I run `bin/rails generate devise User --skip-specs` 8 | Then the output should not contain "invoke specs" 9 | 10 | Scenario: running a devise generator with --no-specs 11 | When I run `bin/rails generate devise User --no-specs` 12 | Then the output should not contain "invoke specs" 13 | -------------------------------------------------------------------------------- /features/specs_pass.feature: -------------------------------------------------------------------------------- 1 | Feature: Specs pass 2 | 3 | Background: 4 | Given I set up devise-specs 5 | 6 | Scenario: specs pass with Factory Bot installed 7 | Given I install factory_bot_rails 8 | And I run `bin/rails generate devise Member` 9 | And I set up devise 10 | When I run `bundle exec rspec spec/features` 11 | Then the examples should all pass 12 | 13 | Scenario: specs pass with Fabrication installed 14 | Given I install fabrication 15 | And I run `bin/rails generate devise Member` 16 | And I set up devise 17 | When I run `bundle exec rspec spec/features` 18 | Then the examples should all pass 19 | -------------------------------------------------------------------------------- /features/step_definitions/devise-specs_steps.rb: -------------------------------------------------------------------------------- 1 | Given(/^I set up devise\-specs$/) do 2 | run_command_and_stop 'rails new . --skip-spring' 3 | 4 | append_to_file 'Gemfile', <<~RUBY 5 | gem 'devise' 6 | gem 'devise-specs', path: '../..' 7 | gem 'rspec-rails' 8 | RUBY 9 | 10 | # define root route 11 | copy '%/routes.rb', 'config/routes.rb' 12 | 13 | run_command_and_stop 'bundle install' 14 | run_command_and_stop 'bin/rails generate rspec:install' 15 | run_command_and_stop 'bin/rails generate devise:install' 16 | end 17 | 18 | Given(/^I install (.*)$/) do |gem| 19 | append_to_file 'Gemfile', "gem '#{gem}'\n" 20 | 21 | run_command_and_stop 'bundle install' 22 | end 23 | 24 | Given(/^I set up devise$/) do 25 | run_command_and_stop 'bin/rails db:migrate RAILS_ENV=test' 26 | run_command_and_stop 'bin/rails generate controller Home index' 27 | 28 | # insert flash messages 29 | copy '%/application.html.erb', 'app/views/layouts/application.html.erb' 30 | 31 | # insert authentication links 32 | copy '%/index.html.erb', 'app/views/home/index.html.erb' 33 | 34 | # configure action_mailer host 35 | copy '%/action_mailer.rb', 'config/initializers/action_mailer.rb' 36 | end 37 | -------------------------------------------------------------------------------- /features/step_definitions/rspec_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^the example(?:s)? should(?: all)? pass$/ do 2 | step %q{the output should contain "0 failures"} 3 | step %q{the output should not contain "0 examples"} 4 | step %q{the exit status should be 0} 5 | end 6 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'aruba/cucumber' 2 | 3 | Aruba.configure do |config| 4 | config.exit_timeout = 300 5 | end 6 | 7 | Before do 8 | delete_environment_variable 'RUBYOPT' 9 | delete_environment_variable 'BUNDLE_GEMFILE' 10 | end 11 | -------------------------------------------------------------------------------- /fixtures/action_mailer.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | config.action_mailer.default_url_options = { host: 'localhost', port: 3001 } 3 | end 4 | -------------------------------------------------------------------------------- /fixtures/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | devise-specs 5 | 6 | 7 | 8 |

<%= notice %>

9 |

<%= alert %>

10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /fixtures/index.html.erb: -------------------------------------------------------------------------------- 1 | <% if member_signed_in? %> 2 | <%= link_to 'Sign Out', destroy_member_session_path, method: :delete %> 3 | <% else %> 4 | <%= link_to 'Sign Up', new_member_registration_path %> 5 | <%= link_to 'Sign In', new_member_session_path %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /fixtures/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root 'home#index' 3 | end 4 | -------------------------------------------------------------------------------- /lib/devise-specs.rb: -------------------------------------------------------------------------------- 1 | require 'devise/specs/railtie' 2 | -------------------------------------------------------------------------------- /lib/devise/specs/railtie.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Specs 3 | class Railtie < Rails::Railtie 4 | generators do 5 | require 'generators/devise/devise_generator' 6 | 7 | Devise::Generators::DeviseGenerator.hook_for :specs, type: :boolean, default: true 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/generators/devise/specs_generator.rb: -------------------------------------------------------------------------------- 1 | module Devise 2 | module Generators 3 | class SpecsGenerator < Rails::Generators::NamedBase 4 | ATTRIBUTES = %( 5 | email { 'username@example.com' } 6 | password { 'password' } 7 | ).freeze 8 | 9 | source_root File.expand_path('../templates', __FILE__) 10 | 11 | def require_support_files 12 | uncomment_lines 'spec/rails_helper.rb', /spec.*support.*rb.*require/ 13 | end 14 | 15 | def insert_fixture_replacement_attributes 16 | return if behavior == :revoke 17 | 18 | if fixture_replacement == :factory_bot 19 | insert_factory_bot_attributes 20 | elsif fixture_replacement == :fabrication 21 | insert_fabrication_attributes 22 | end 23 | end 24 | 25 | def create_factory_bot_config_file 26 | if fixture_replacement == :factory_bot 27 | template 'factory_bot.rb', 'spec/support/factory_bot.rb' 28 | end 29 | end 30 | 31 | def create_devise_config_file 32 | template 'devise.rb', 'spec/support/devise.rb' 33 | end 34 | 35 | def create_specs 36 | template 'resource_signs_up_spec.rb', 37 | "spec/features/#{singular_name}_signs_up_spec.rb" 38 | 39 | template 'resource_signs_in_spec.rb', 40 | "spec/features/#{singular_name}_signs_in_spec.rb" 41 | 42 | template 'resource_signs_out_spec.rb', 43 | "spec/features/#{singular_name}_signs_out_spec.rb" 44 | 45 | template 'resource_resets_password_spec.rb', 46 | "spec/features/#{singular_name}_resets_password_spec.rb" 47 | end 48 | 49 | private 50 | 51 | def fixture_replacement 52 | Rails.application.config.generators.rails[:fixture_replacement] 53 | end 54 | 55 | def insert_factory_bot_attributes 56 | path = "spec/factories/#{plural_name}.rb" 57 | attrs = ATTRIBUTES.gsub(/^ {4}/, '') 58 | after = "factory :#{singular_name} do" 59 | 60 | insert_into_file path, attrs, after: after 61 | end 62 | 63 | def insert_fabrication_attributes 64 | path = "spec/fabricators/#{singular_name}_fabricator.rb" 65 | attrs = ATTRIBUTES.gsub(/^ {6}/, '') 66 | after = "Fabricator(:#{singular_name}) do" 67 | 68 | insert_into_file path, attrs, after: after 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/generators/devise/templates/devise.rb.tt: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include Devise::Test::IntegrationHelpers, type: :feature 3 | end 4 | -------------------------------------------------------------------------------- /lib/generators/devise/templates/factory_bot.rb.tt: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include FactoryBot::Syntax::Methods 3 | end 4 | -------------------------------------------------------------------------------- /lib/generators/devise/templates/resource_resets_password_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.feature '<%= human_name %> resets a password' do 4 | scenario '<%= singular_name %> enters a valid email' do 5 | <% if fixture_replacement == :factory_bot -%> 6 | <%= singular_name %> = create :<%= singular_name %> 7 | <% elsif fixture_replacement == :fabrication -%> 8 | <%= singular_name %> = Fabricate :<%= singular_name %> 9 | <% end -%> 10 | 11 | visit new_<%= singular_name %>_password_path 12 | 13 | fill_in 'Email', with: <%= singular_name %>.email 14 | click_button 'Send me reset password instructions' 15 | 16 | expect(page).to have_text 'You will receive an email with instructions' 17 | expect(page).to have_current_path new_<%= singular_name %>_session_path 18 | end 19 | 20 | scenario '<%= singular_name %> enters an invalid email' do 21 | visit new_<%= singular_name %>_password_path 22 | 23 | fill_in 'Email', with: 'username@example.com' 24 | click_button 'Send me reset password instructions' 25 | 26 | expect(page).to have_text 'Email not found' 27 | end 28 | 29 | scenario '<%= singular_name %> changes password' do 30 | <% if fixture_replacement == :factory_bot -%> 31 | token = create(:<%= singular_name %>).send_reset_password_instructions 32 | <% elsif fixture_replacement == :fabrication -%> 33 | token = Fabricate(:<%= singular_name %>).send_reset_password_instructions 34 | <% end -%> 35 | 36 | visit edit_<%= singular_name %>_password_path(reset_password_token: token) 37 | 38 | fill_in 'New password', with: 'p4ssw0rd' 39 | fill_in 'Confirm new password', with: 'p4ssw0rd' 40 | click_button 'Change my password' 41 | 42 | expect(page).to have_text 'Your password has been changed successfully.' 43 | expect(page).to have_current_path root_path 44 | end 45 | 46 | scenario 'password reset token is invalid' do 47 | visit edit_<%= singular_name %>_password_path(reset_password_token: 'token') 48 | 49 | fill_in 'New password', with: 'p4ssw0rd' 50 | fill_in 'Confirm new password', with: 'p4ssw0rd' 51 | click_button 'Change my password' 52 | 53 | expect(page).to have_text 'Reset password token is invalid' 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/generators/devise/templates/resource_signs_in_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.feature '<%= human_name %> signs in' do 4 | scenario 'with valid credentials' do 5 | <% if fixture_replacement == :factory_bot -%> 6 | <%= singular_name %> = create :<%= singular_name %> 7 | <% elsif fixture_replacement == :fabrication -%> 8 | <%= singular_name %> = Fabricate :<%= singular_name %> 9 | <% end -%> 10 | 11 | visit new_<%= singular_name %>_session_path 12 | 13 | fill_in 'Email', with: <%= singular_name %>.email 14 | fill_in 'Password', with: <%= singular_name %>.password 15 | click_button 'Log in' 16 | 17 | expect(page).to have_text 'Signed in successfully.' 18 | expect(page).to have_link 'Sign Out' 19 | expect(page).to have_current_path root_path 20 | end 21 | 22 | scenario 'with invalid credentials' do 23 | <% if fixture_replacement == :factory_bot -%> 24 | <%= singular_name %> = build :<%= singular_name %> 25 | <% elsif fixture_replacement == :fabrication -%> 26 | <%= singular_name %> = Fabricate.build :<%= singular_name %> 27 | <% end -%> 28 | 29 | visit new_<%= singular_name %>_session_path 30 | 31 | fill_in 'Email', with: <%= singular_name %>.email 32 | fill_in 'Password', with: <%= singular_name %>.password 33 | click_button 'Log in' 34 | 35 | expect(page).to have_text 'Invalid Email or password.' 36 | expect(page).to have_no_link 'Sign Out' 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/generators/devise/templates/resource_signs_out_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.feature '<%= human_name %> signs out' do 4 | scenario '<%= singular_name %> signed in' do 5 | <% if fixture_replacement == :factory_bot -%> 6 | <%= singular_name %> = create :<%= singular_name %> 7 | <% elsif fixture_replacement == :fabrication -%> 8 | <%= singular_name %> = Fabricate :<%= singular_name %> 9 | <% end -%> 10 | 11 | sign_in <%= singular_name %> 12 | 13 | visit root_path 14 | 15 | click_link 'Sign Out' 16 | 17 | expect(page).to have_text 'Signed out successfully.' 18 | expect(page).to have_no_link 'Sign Out' 19 | expect(page).to have_current_path root_path 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/generators/devise/templates/resource_signs_up_spec.rb.tt: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.feature '<%= human_name %> signs up' do 4 | scenario 'with valid data' do 5 | visit new_<%= singular_name %>_registration_path 6 | 7 | fill_in 'Email', with: 'username@example.com' 8 | fill_in 'Password', with: 'password' 9 | fill_in 'Password confirmation', with: 'password' 10 | click_button 'Sign up' 11 | 12 | expect(page).to have_text 'Welcome! You have signed up successfully.' 13 | expect(page).to have_link 'Sign Out' 14 | expect(page).to have_current_path root_path 15 | end 16 | 17 | scenario 'with invalid data' do 18 | visit new_<%= singular_name %>_registration_path 19 | 20 | click_button 'Sign up' 21 | 22 | expect(page).to have_text "Email can't be blank" 23 | expect(page).to have_text "Password can't be blank" 24 | expect(page).to have_no_link 'Sign Out' 25 | end 26 | end 27 | --------------------------------------------------------------------------------