使用Rspec在多个上下文之间传递变量

时间:2017-09-05 02:11:01

标签: ruby-on-rails rspec ruby-on-rails-5 factory-bot

我正在使用FactoryGirlRspec编写一些测试。

规格/工厂/ students.rb

FactoryGirl.define do
  factory :student do
  end

  factory :student_with_profile_and_identity, class: 'Student' do
    after(:create) do |student|
      create(:profile, profileable: student)
      create(:student_identity, student: student)
    end
  end
end

规格/工厂/ profiles.rb

FactoryGirl.define do
  factory :profile do
    birthday { Faker::Date.birthday(15, 150) }
    sequence(:email) { |i| "profile_#{i}@email.com" }
    first_name { Faker::Name.first_name }
    last_name { Faker::Name.first_name }
    password { Faker::Internet.password(6, 72, true, true) }
  end
end

规格/工厂/ student_identities.rb

FactoryGirl.define do
  factory :student_identity do
    provider { ['facebook.com', 'google.com', 'twitter.com'].sample }
    uid { Faker::Number.number(10) }
  end
end

规格/请求/ authorizations_spec.rb

require 'rails_helper'

RSpec.describe 'Authorizations', type: :request do
  describe 'POST /v1/authorizations/sign_in' do
    let!(:student) { create(:student_with_profile_and_identity) }

    context 'when the request is valid' do
      subject do
        post '/v1/authorizations/sign_in',
             params: credentials
      end

      context "user signs up via social network" do
        let(:credentials) do
          {
            authorization: {
              student: {
                profile_attributes: {
                  email: student.profile.email
                },
                student_identities_attributes: {
                  provider: student.student_identities[0].provider,
                  uid: student.student_identities[0].uid
                }
              }
            }
          }
        end

        it 'returns an authentication token' do
          subject
          p "1 student.profile.inspect #{student.profile.inspect}"
          expect(json['token']).to(be_present)
        end
      end

      context 'when the user has already an account' do
        let(:credentials) do
          {
            authorization: {
              email: student.profile.email,
              password: student.profile.password
            }
          }
        end

        it 'returns an authentication token' do
          p "2 student.profile.inspect #{student.profile.inspect}"
          subject
          expect(json['token']).to(be_present)
        end
      end
    end
  end
end

几乎所有测试都在通过......问题在于:

它正在每个环境中创建一个新的学生。我希望let!(:student) { ... }类似于“singleton”,换句话说,一旦在let!(:student) { create(:student_with_profile_and_identity) }创建/定义它,它将不再被调用。

Ex:日志是这样的:

"1 student.profile.inspect #<Profile id: 1, email: \"profile_1@email.com\", profileable_type: \"Student\", profileable_id: 1>"

"2 student.profile.inspect #<Profile id: 2, email: \"profile_2@email.com\", profileable_type: \"Student\", profileable_id: 2>"

虽然我希望实例是相同的。

我错过了什么吗?

1 个答案:

答案 0 :(得分:0)

在RSpec中,let and let!是相同的,除了let是懒惰的,而let!是渴望的:

  

使用let定义memoized辅助方法。该值将在同一示例中的多个调用中缓存,但不会跨示例缓存。

     

请注意,let是惰性求值的:直到第一次调用它定义的方法时才会对它进行求值。您可以使用let!在每个示例之前强制调用方法。

如果您想要通过所有示例保留某些内容,可以使用before hook ... before(:context)声音,就好像它可能是您想要的。您可以设置一个在before块中记忆的辅助方法,以避免在任何地方使用实例变量(每this comment):

def student
  @student ||= create(:student_with_profile_and_identity)
end

before(:context) do
  student # force student creation
end