0

I set up a simple controller with its according feature_spec:

stamps_controller.rb

class StampsController < ApplicationController
  def new
    @stamp = Stamp.new
  end

  def create
    @stamp = Stamp.new(stamp_params)

    if @stamp.save
      redirect_to(stamp_url(@stamp.id), status: 201)
    else
      render 'new'
    end
  end

  def show
    @stamp = Stamp.find(params[:id])
  end

  private

  def stamp_params
    params.require(:stamp).permit(::percentage)
  end
end

specs/requests/stamps_request_spec.rb

RSpec.describe 'stamp requests', type: :request do
  describe 'stamp creation', js: true do
    before do
      FactoryBot.create_list(:domain, 2)
      FactoryBot.create_list(:label, 2)
    end

    it 'allows users to create new stamps' do
      visit new_stamp_path
      expect(page).to have_content('Percentage')

      find('#stamp_percentage').set('20')

      click_button 'Create'

      expect(current_path).to eq(stamp_path(Stamp.first.id))
    end
  end
end

According to the capybara docs:

Capybara automatically follows any redirects, and submits forms associated with buttons.

But this does not happen in the test, instead it throws an error:

expected: "/stamps/1

got: "/stamps"

The results are obvious: it successfully creates the stamp but fails to redirect to the new stamp. I also confirmed this by using binding.pry.

Why wouldn't capybara follow the redirect as described in the docs?


Sidenotes:

  1. it even fails if I use the normal driver instead of js
  2. I've looked into lots of SO questions and docs, finding nothing useful. One potential attempt I was unable to grasp was an answer with no specifics of how to implement it.
  3. my configs:

support/capybara.rb

require 'capybara/rails'
require 'capybara/rspec'

Capybara.server = :puma
Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(app, browser: :firefox, marionette: true)
end

Capybara.javascript_driver = :selenium

RSpec.configure do |config|
  config.include Capybara::DSL
end

spec_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'
require 'factory_bot_rails'
require 'pundit/matchers'

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

RSpec.configure do |config|
  # .
  # unrelated stuff
  # .
end
Community
  • 1
  • 1
davegson
  • 8,205
  • 4
  • 51
  • 71

3 Answers3

4

You have a number of issues in your test.

First, Capybara is not meant to be used in request specs - https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec - but should instead be used with feature/system tests. Once you've fixed that you should no longer need to include Capybara into every RSpec test and should remove the config.include Capybara::DSL from you config (when you require capybara/rspec it includes Capybara into the test types it should be included in - https://github.com/teamcapybara/capybara/blob/master/lib/capybara/rspec.rb#L10)

Second, click_button is not guaranteed to wait for any actions it triggers to complete. Because of that you need to wait for a visual page change before attempting to access any database objects that would be created by that action (technically you really shouldn't be doing direct DB access in feature specs at all but if you're going to ...)

  click_button 'Create' 
  expect(page).to have_text('Stamp created!') # Whatever message is shown after creation
  # Now you can safely access the DB for the created stamp

Third, as pointed out by @chumakoff you should not be using static matchers with Capybara and should instead be using the matchers provided by Capybara

  click_button 'Create' 
  expect(page).to have_text('Stamp created!') # Whatever message is shown after creation
  expect(page).to have_path(stamp_path(Stamp.first.id))

Finally, you should look at your test.log and use save_and_open_screenshot to see what your controllers actually did - It's and there's actually an error being raised on creation which is causing your app to redirect to /stamps and display the error message (would also imply your test DB isn't actually being reset between tests, or the factories you show are creating nested records, etc).

Update: After rereading your controller code I noticed the that you're passing a 201 status code to redirect_to. 201 won't actually do a redirect - From the redirect_to docs - https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-redirect_to

Note that the status code must be a 3xx HTTP code, or redirection will not occur.

Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78
  • Thank you for your extensive answer. Regarding 1., I'll happily take the word from the expert rather than [the blog post.](https://medium.com/carwow-product-engineering/f4ebbe13f983) But if feature specs are implemented, don't they cover everything that request specs would do? Could you maybe refer sources to see the value in both? Or even a link to a great project which implemented both test types? - 2. So a page expectation suffices and waits for the visual change? - 2.5 I will try to avoid DB calls ;) - and 3., I'll gladly dig into all appropriate matchers – davegson Aug 22 '18 at 13:05
  • Sadly, the problem still occurs with the correct matcher (for the sake of it I added `sleep(2)` before the expectation). My browser freezes at https://imgur.com/a/XoWhP0R - the structure improvements are not done yet, but I doubt that will change much – davegson Aug 22 '18 at 13:10
  • 2
    @TheCha͢mp request specs are useful for testing APIs (and I have no idea what that blog post is talking about since they're actually writing feature specs - as it states in the wrapping up section). Feature specs are more from the user perspective and browser based - https://relishapp.com/rspec/rspec-rails/docs/feature-specs/feature-spec. I've added an update with what is probably the cause of your redirect not happening, however the rest of the issues I listed are still valid for your code. – Thomas Walpole Aug 22 '18 at 14:29
  • nice catch! That was indeed the issue. And I will gladly fix all other issues as well. I'll switch all current request specs to feature specs since that is what they are. I'm new to capybara, but I'll surely learn the conventions over time – davegson Aug 22 '18 at 14:41
2

The problem might be that it takes some time for current_path to change after the form is submitted. Your code would work if you put sleep(x) before expect(current_path).

Instead, you should use methods that have so-called "waiting behaviour", such as has_current_path?, have_current_path, assert_current_path:

expect(page).to have_current_path(stamp_path(Stamp.first.id))

or

expect(page.has_current_path?(stamp_path(Stamp.first.id))).to eq true
chumakoff
  • 6,807
  • 2
  • 23
  • 45
  • Sadly, the problem still occurs with the correct matcher (for the sake of it I added sleep(2) before the expectation). My browser freezes at https://imgur.com/a/XoWhP0R – davegson Aug 22 '18 at 13:12
  • Thomas Walpole found the underlying issue, still thanks for your help! – davegson Aug 22 '18 at 14:42
0

For anyone else coming here, another possible solution is to increase your wait time. Either globally or per click

# Globally
Capybara.default_max_wait_time = 5

# Per Click
find("#my-button").click(wait: 5)


Weston Ganger
  • 6,324
  • 4
  • 41
  • 39