为什么在包含值时,在rspec规范中将变量评估为nil?

时间:2013-12-10 06:11:24

标签: ruby-on-rails rspec

我根本不明白这一点:

简而言之,这个

it "should assign @user_friendship_1 to the instance variable #|@user_friendships|" do
    var = "something"
    ap @user_friendship_1
    var.should == @user_friendship_1
end

导致了这个奇怪的错误:

.#<UserFriendship:0x000001031fb6f8> {
            :id => 1,
     :friend_id => 2,
       :user_id => 1,
    :created_at => Tue, 10 Dec 2013 05:56:54 UTC +00:00,
    :updated_at => Tue, 10 Dec 2013 05:56:54 UTC +00:00,
         :state => "accepted"
}
....

Failures:

  1) UserFriendshipsController: When four valid User instances exist, @user_1, @user_2, @user_3, @user_4, and when logged in as @user_, and when a UserFriendship, @user_friendship_1, is joining @user_1 with @user_2, a PATCH request to the #edit action with the :id of @user_friendship_1 should assign @user_friendship_1 to the instance variable #|@user_friendship_1|
     Failure/Error: assigns(:dfsdf).should == @user_frienship_1
       expected: nil
            got: "something" (using ==)

我的意思是什么? ap证明@ user_friendship_1是一个哈希值,并且在期望值内的nil处评估完全相同的变量?究竟是什么?

规格:

require 'spec_helper'
require 'ap'

describe UserFriendshipsController do


   context "\b: When four valid User instances exist, @user_1, @user_2, @user_3, @user_4" do
        before do
            @user_1 = FactoryGirl.create(:user_with_all_valid)
            @user_1.reload

            @user_2 = FactoryGirl.create(:user_with_all_valid_two)
            @user_2.reload

            @user_3 = FactoryGirl.create(:user_with_all_valid_three)
            @user_3.reload

            @user_4 = FactoryGirl.create(:user_with_all_valid_four)
            @user_4.reload
        end

        context "\b, and when not logged in" do
            before do
            end

            context "\b, a PUT request to the #accept action" do
                before do
                    put :accept, id: 1
                end

                it "should redirect to the login page" do
                    response.response_code.should == 302
                    response.should be_redirect
                    response.should redirect_to new_user_session_path
                end
            end 
        end

        context "\b, and when logged in as @user_1" do
            before do
                sign_in @user_1
            end

            context "\b, and when a UserFriendship, @user_friendship_1, is joining @user_1 with @user_2" do
                before do
                    @user_friendship_1 = FactoryGirl.create(:pending_user_friendship, user_id: @user_1.id, friend_id: @user_2.id)
                    @user_friendship_1.reload
                end

                it "@user_friendship_1.state should equal 'pending' by default, when it is created" do
                    @user_friendship_1.state.should == 'pending'
                end

                context "\b, a PATCH request to the #edit action with the :id of @user_friendship_1" do
                    before do
                        patch :edit, id: @user_friendship_1.id
                        @user_friendship_1.reload
                    end

                    it "should assign @user_friendship_1 to the instance variable #|@user_friendship_1|" do
                        puts "****"
                        ap @user_frienship_1
                    end
                    it "should assign @user_friendship_1 to the instance variable #|@user_friendship_1|" do
                        assigns(:dfsdf).should == @user_frienship_1
                    end
                    it "should assign @user_friendship_1 to the instance variable #|@user_friendship_1|" do

                        puts @user_friendships_1.inspect

                        assigns(:dfsdf).should_not == @user_frienship_1

                    end
                end

                context "\b, a PUT request to the #accept action with the :id of @user_friendship_1" do
                    before do
                        put :accept, id: @user_friendship_1.id
                        @user_friendship_1.reload
                    end

                    it "should assign @user_friendship_1 to the instance variable #|@user_friendships|" do
                        var = "something"

                        ap @user_friendship_1
                        var.should == @user_friendship_1
                    end


                    it "should change @user_friendship_1.state from 'pending' to 'accepted' " do
                        @user_friendship_1.state.should == 'accepted'   
                    end

                    it "should populate the flash[:success] with the success message '' " do
                        flash[:success].should == "You are now friends with " + @user_2.first_name + "!"
                    end

                    it "should redirect to the show route of that friendship" do
                        response.should redirect_to user_friendship_path 
                    end
                end 
            end
      end
  end

工厂:

FactoryGirl.define do
    factory :status_one, class: Status do
    #   name "Here is numerano Uno Status!"
        content "This is definitely the number one status! One, as in 1!"
    end

    factory :user_with_all_valid, class: User do
        first_name "jimmy"
        last_name "Thehat"
        profile_name "Jimbohatboy"  
        sequence (:email){ |n| "awesomedog" + n.to_s + "@hotmail.co.uk" }
        password "thisisasupersecretpassword12234234"
        password_confirmation "thisisasupersecretpassword12234234"
    end

    # look into the following:
    # we can nest factories
    # we can use sequences to increment email addresses and make them unique
    # we can set up associations (below)

    # use fresh users here, that's what'ss causing the error

    factory :user_friendship_1, class: UserFriendship do
        association :user, factory: :user_with_all_valid_five
        # or :user_id, factory: :user_with_all_valid[id]
        association :friend, factory: :user_with_all_valid_six
        # or :friend_id, factory: :user_with_all_valid_two[id]

        factory :pending_user_friendship do
            state 'pending'
        end

        factory :requested_user_friendship do
            state 'requested'
        end

        factory :accepted_user_friendship do
            state 'accepted'
        end
    end

    factory :user_with_all_valid_two, class: User do
        first_name "Mattychips"
        last_name "Matthews"
        profile_name "Chipboy76"    
        email "Chips@hotmail.co.uk"
        password "yeahyeahsupersecret"
        password_confirmation "yeahyeahsupersecret"
    end

    factory :user_with_all_valid_three, class: User do
        first_name "Ratty"
        last_name "Excellent"
        profile_name "Ratexcellent" 
        email "rat@hotmail.co.uk"
        password "hello_987"
        password_confirmation "hello_987"
    end

    factory :user_with_all_valid_four, class: User do
        first_name "Sylvia"
        last_name "Plath"
        profile_name "collosus64676421" 
        email "collosus@987987.co.uk"
        password "thisisasupersecretpassword12234234"
        password_confirmation "thisisasupersecretpassword12234234"
    end 
end

模型:

class UserFriendship < ActiveRecord::Base
    # sets the 'state' column to pending whenever a new model (UserFriendship) is created
    state_machine :state, initial: :pending do

        after_transition on: :accept, do: :send_accept_email
=begin 
    this creates an action called 'accepted!' and changes the 'state' column to 'accepted' when called
    it's basically a shorthand for this method (the exclamation mark, nothing more than a practise that doesn't
    change the code) is automatically added to the method name. This is because it's a potentially
    dangerous bang method that we've made:

    event :accept do
        transition any => :accepted
    end 

    is the same as writing:

    def accept!
        self.state == 'accepted'
    end

=end
        event :accept do
            transition any => :accepted
        end

        state :requested


    end


    belongs_to :user

=begin this right here, is where the friend model comes into existance.

    belongs_to :friend, class_name: 'User'

    is basically a shorthand for creating a new model file, friend.rb and populating it with the same
    contents as user.rb (I think..maybe just the attributes and certain associations)
=end
    belongs_to :friend, class_name: 'User', foreign_key: 'friend_id'

控制器:

class UserFriendshipsController < ApplicationController
    before_filter :authenticate_user!, only: [:new, :create, :index, :accept]

    def accept
        # why does this need to be scoped to the current user? This does the same thing:
        #   
        # @user_friendships = UserFriendship.find_by_id(params[:id])
        #
        # exactly the same result. Find out the benefit and the reasoning.

        @user_friendships = current_user.user_friendships.find_by_id(params[:id])

        # a shorthand for if @user_friendships.accept! == true . All methods evaluate to boolean true
        # 'true' if they are successful, and false if otherwise. This way, we can do a simple statement:
        if @user_friendships.accept!
            flash[:success] = "You are now friends with " + @user_friendships.friend.first_name + "!"
        else
            flash[:error] = "Friendship between you, " + current_user.profile_name + " and " + @user_friendships.friend.first_name + "could not be created"
        end

        redirect_to user_friendship_path
    end

    def edit
        @dfsdf = "cheese"       
    end

    def index
        @user_friendships = current_user.user_friendships.load
    end

    def new 
        if params[:friend_id]

            @friend = User.find_by_profile_name(params[:friend_id])
            @user_friendship = current_user.user_friendships.new(friend: @friend)

        else
            flash[:error] = "Friend required"
        end

        if @friend

        else
            flash[:notice] = "That user could not be found"
            render file: 'public/404', status: 404
        end 
=begin

        rescue ActiveRecord::RecordNotFound
            flash[:notice] = "That user could not be found"
            render file: 'public/404', status: 404
=end            


    end

    def create

        if params[:user_friendship][:friend_id]

            @friend = User.find_by_id(params[:user_friendship][:friend_id])
            logger.fatal @friend.inspect
        else

        end

        if @friend

            current_user.user_friendships.create(friend: @friend)

            flash[:success] = @friend.profile_name + " added as friend!"
            redirect_to profile_path(@friend.profile_name), status: 302

        else
            flash[:error] = "Friend required!"
            redirect_to root_path, status: 302
        end 
    end

end

spec_helper:

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rspec'
require 'ap'

def set(factory)
  @user = FactoryGirl.create(factory) 
end


# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }

# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

RSpec.configure do |config|
  config.include Capybara::DSL
=begin
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  config.after(:suite) do
#   DatabaseCleaner.strategy = :transaction
#   DatabaseCleaner.clean_with(:truncation)
    DatabaseCleaner.clean
  end
=end

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  # config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # config.include RSpec::Rails::RequestExampleGroup, type: :feature

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # If true, the base class of anonymous controllers will be inferred
  # automatically. This will be the default behavior in future versions of
  # rspec-rails.
  config.infer_base_class_for_anonymous_controllers = false

  # Run specs in random order to surface order dependencies. If you find an
  # order dependency and want to debug it, you can fix the order by providing
  # the seed, which is printed after each run.
  #     --seed 1234
  config.order = "random"
end

2 个答案:

答案 0 :(得分:1)

首先,您最初引用的示例不是失败的示例。失败的样本位于“PATCH”上下文中,基于失败消息中输出的描述链。但是,该上下文中的it块都没有将"something"分配给var或输出var

并且,感谢other answer,您可以看到示例中出现@user_frienship-1错字:

it "should assign @user_friendship_1 to the instance variable #|@user_friendship_1|" do
  assigns(:dfsdf).should == @user_frienship_1
end

由于未定义的实例变量的计算结果为nil,因此您将遇到问题。

顺便说一下,这是为什么避免使用before在RSpec测试中分配实例变量以支持使用let来分配常规变量作为无效引用的一个例子。常规变量将生成错误而不是静默返回nil

答案 1 :(得分:0)

您在这些规范中将@user_friendship_1错误地标记为@user_frienship_1几次。我怀疑这可能是相关的。