jeudi 20 juillet 2017

User update test fails when more users present

Working through the tutorial in Chapter 10, everything was working and testing green with no problems. However, when I add more users as per Listing 10.47, the 'update user with friendly forwarding' test starts failing.

 FAIL["test_successful_edit_with_friendly_forwarding", UsersEditTest, 1.8292690003290772]
 test_successful_edit_with_friendly_forwarding#UsersEditTest (1.83s)
        Expected response to be a redirect to <http://ift.tt/1NpgCsS; but was a redirect to <http://ift.tt/1D1DOKt;.
        Expected "http://ift.tt/1NpgCJ6" to be === "http://ift.tt/1D1DOKz".
        test/integration/users_edit_test.rb:35:in `block in <class:UsersEditTest>'

I've tested this a fair bit, and when I remove users from users.yml, the test passes again. So I have a bug in my code which is only appearing when there are more than 2 users in the app. This was my test:

users_edit_test.rb

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test 'unsuccessful edit' do
    log_in_as(@user)
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name: '',
                                              email: 'foo@invalid',
                                              password:              'foo',
                                              password_confirmation: 'bar' } }
    assert_template 'users/edit'
    assert_select 'div.alert',
                  'The form contains 4 errors.'
  end

  # This is the failing test
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user #The test is failing on this line
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
end

Looking at the error, it appears that user_path(@user) is returning the wrong user. I've tested and it looks like the patch request is failing, and the user name and email are the original user name and password, not the ones set in the patch request. This may be because it is patching the wrong user, but I can't understand why or how. When I test to figure out the value of @user, it's always resolving to the first user in users.yml, as far as I can tell.

users.yml

michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
  admin: true

archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>

lana:
  name: Lana Kane
  email: hands@example.gov
  password_digest: <%= User.digest('password') %>

malory:
  name: Malory Archer
  email: boss@example.gov
  password_digest: <%= User.digest('password') %>

<% 30.times do |n| %>
user_<%= n %>:
  name:  <%= "User #{n}" %>
  email: <%= "user-#{n}@example.com" %>
  password_digest: <%= User.digest('password') %>
<% end %>

If I remove the additional users and leave only the first two, the test now passes. Which is the state we were in when I wrote the test. Now, my pagination test fails instead because there aren't enough users for pagination. That is as it should be.

I'm fairly new to Rails (what which doing the tutorial and all), and particularly new to testing. I've tried various techniques in changing the test temporarily to figure out what is going wrong, but I honestly can't trace where the bug is. I've compared all my code to the code provided and can't see any typos or mistakes, but there has to be something wrong.

Strangely, I can't replicate the bug at all in development or in production on Heroku. The application behaves as it should as far as I can tell both as a user and in looking at logs. But the test continues to fail – I must have introduced a bug somewhere. Or my test is wrong but I can't see anything different from the code in the tutorial.

I've seen similar questions such as this one, but the issue there was with the redirect which as far as I have tested is working. In addition, if I change my test to take away the redirect, and test only the user update, the test still fails. So the problem may well be in my user update method.

user_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user, only: [:edit, :update]
  before_action :admin_user,   only: :destroy

  def index
    @users = User.paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      log_in @user
      flash[:success] = 'Welcome to the Sample App!'
      redirect_to @user
    else
      render 'new'
    end
  end

  def edit
    @user = User.find_by(params[:user_id])
  end

  def update
    @user = User.find_by(params[:user_id])
    if @user.update_attributes(user_params)
      flash[:success] = 'Profile Updated'
      redirect_to @user
    else
      render 'edit'
    end
  end

  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User deleted"
    redirect_to users_url
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end

    # Before filters

    # Confirms a logged-in user.
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

    # Confirms the correct user.
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end

    # Confirms an admin user.
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end
end

Aucun commentaire:

Enregistrer un commentaire