2
votes

I'm using Rspec 3.0 and capybara 2.4.3. I made a feature spec in spec/features/ named challenge_users_spec.rb

require "rails_helper"

feature "Challenge users" do
  let!(:challenge) { create(:challenge)}
  let(:user) { create(:user)}

  context "as a registered user" do
    before :each do
      sign_in(user)
    end

    scenario "challenges himself" do

      expect(current_path).to eq root_path
      expect(page).to have_content challenge.name

      click_button 'Accept'

      expect(page).to have_content 'You accepted the challenge!'
      expect(user.accepted_dares.count).to eq 1
    end

    scenario 'challenges other user' do
      expect(current_path).to eq root_path

      click_button 'Challenge others'

      expect(current_path).to eq new_challenge_dare_path(challenge_id: challenge.id)
      expect(page).to have_content 'Challenge with a bet!'
    end
  end
end

In the first scenario, page renders the right flash msg but doesn't update the User. In the second scenario, after clicking 'Challenge others', the current_path is set to "/" instead of "challenges/:id/dares/new", thus failing last two expectations. Dares model is a nested route:

  resources :challenges do
    resources :dares
  end

The controller methods are mostly standard CRUD methods.

Here is the Rspec log:

Failures:

  1) Challenge users as a registered user challenges himself
     Failure/Error: expect(user.accepted_dares.count).to eq 1

       expected: 1
            got: 0

       (compared using ==)
     # ./spec/features/challenge_users_spec.rb:18:in `block (3 levels) in <top (required)>'

  2) Challenge users as a registered user challenges other user
     Failure/Error: expect(current_path).to eq new_challenge_dare_path(challenge_id: challenge.id)

       expected: "/challenges/2/dares/new"
            got: "/"

       (compared using ==)
     # ./spec/features/challenge_users_spec.rb:26:in `block (3 levels) in <top (required)>'

Finished in 0.91199 seconds (files took 3.62 seconds to load)
2 examples, 2 failures

Failed examples:

rspec ./spec/features/challenge_users_spec.rb:10 # Challenge users as a registered user challenges himself
rspec ./spec/features/challenge_users_spec.rb:21 # Challenge users as a registered user challenges other user

Models:

Dare

class Dare < ActiveRecord::Base

  belongs_to :challenge, counter_cache: true
  belongs_to :acceptor, class_name: "User"
  belongs_to :challenger, class_name: "User"

  has_many :votes

  before_save :change_status
  before_save :create_start_date
  before_save :set_proof_array

  def set_proof_array
    if self.utube_link.nil?
      self.utube_link = []
      save!
    end
  end

  def create_start_date
    if status == 'Accepted' && start_date.blank?
      self.start_date = DateTime.now
    end
  end

  def change_status
    if status.blank?
      if self.acceptor_id == self.challenger_id
        self.status = "Accepted"
      else
        self.status = "Pending"
      end
    end
  end
...
end

User

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  has_many :dares, foreign_key: :challenger_id
  has_many :accepted_dares, class_name: "Dare", foreign_key: :acceptor_id
  has_many :challenges, through: :dares
  has_many :votes

  validates :username, presence: true, uniqueness: true, length: { in: 2..50 }

    ....
end

Challenge

class Challenge < ActiveRecord::Base

  has_many :dares
  has_many :users, through: :dares, source: :challenges

  validates :name, presence: true, uniqueness: true, length: { in: 5..100 }
  validates :description, presence: true, length: { in: 10..500 }

  include PgSearch
  pg_search_scope :search, against: [:name, :description],
                  using: {tsearch: {prefix: true, dictionary: "english" }}
  ...

end

EDIT

I didnt mention this but im using Turbolinks in my app. After adding js:true to my tests the second test passes but the first still throws an error:

1) Challenge users as a registered user challenges himself
     Failure/Error: expect(user.reload.accepted_dares.count).to eq 1

       expected: 1
            got: 0

       (compared using ==)
1
Could you add your models whith validation ?Fred Perrin
Added models as requestedSzymon Borucki

1 Answers

5
votes

These are two unrelated problems.

The reason the user model doesn't update is that it is a totally separate instance in memory from the one that is changed on the back end. You need to force it to reload from the database:

expect(user.reload.accepted_dares.count).to eq 1

The reason current_path isn't giving you the expected value is that click_button is an asynchronous operation. Your assertion on current_path will be executed immediately after clicking the button, before the new page has a chance to load.

Many of Capybara's operations (such as find) automatically wait and retry until a condition becomes true (in the case of find it waits for the selector to match something on the page), but because current_path doesn't have any condition, it returns the current value immediately (Capybara doesn't know what you expect the current path to be at the time that the method is called).

A better way to test this would be to look for some content that you expect to only be on the page after the button is clicked and the page reloads. That forces Capybara to wait for that content before proceeding.

In this case, reversing the order of the two assertions you have should do the trick:

expect(page).to have_content 'Challenge with a bet!'
expect(current_path).to eq new_challenge_dare_path(challenge_id: challenge.id)