Ошибка Rspec be_valid при допустимом тестировании

#ruby-on-rails #rspec

#ruby-on-rails #rspec

Вопрос:

Я изучаю TDD с Rails 4 и rspec. Я сделал несколько тестовых примеров для своей пользовательской модели, чтобы проверить длину пароля. Пока у меня есть два теста, которые проверяют, вводил ли пользователь слишком короткий пароль, и тот, в котором пароль составляет от 6 до 10 символов.

Пока что тест «пароль слишком короткий» проходит:

 it "validation says password too short if password is less than 6 characters" do
  short_password = User.create(email: "tester@gmail.com", password: "12345")
  expect(short_password).not_to be_valid
end  
  

Однако в тесте, где у меня есть действительный пароль, он завершается с ошибкой:

 it "validation allows passwords larger than 6 and less than 10" do
  good_password = User.create(email: "tester2@gmail.com", password: "blahblah")
  expect(good_password).to be_valid
end
  

И я получаю эту ошибку:

 Failure/Error: expect(good_password).to be_valid
   expected #<User id: 1, email: "tester2@gmail.com", 
   created_at: "2014-06-21 02:43:42", updated_at: "2014-06-21 02:43:42",
   password_digest: nil, password: nil, password_hash: "$2a$10$7u0xdDEcc6KJcAi32LBW7uzV9n7xYbfOhZWdcOnU5Cdm...",
   password_salt: "$2a$10$7u0xdDEcc6KJcAi32LBW7u"> to be valid, 
   but got errors: Password can't be blank, Password is too short (minimum is 6 characters)
 # ./spec/models/user_spec.rb:12:in `block (3 levels) in <top (required)>'
  

Вот мой код модели:

 class User < ActiveRecord::Base

  has_many :pets, dependent: :destroy
  accepts_nested_attributes_for :pets, :allow_destroy => true
  VALID_EMAIL_REGEX = /A[w -.] @[a-zd-.] .[a-z] z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
  uniqueness: true

  validates :password, presence: true, :length => 6..10, :confirmation => true

  #callbacks
  before_save :encrypt_password
  after_save :clear_password

  #method to authenticate the user and password
  def self.authenticate(email, password)
    user = find_by_email(email)
    if user amp;amp; user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil
    end
  end

  #method to encrypt password
  def encrypt_password
    if password.present?
      self.password_salt = BCrypt::Engine.generate_salt
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end
  end

  #clears password
  def clear_password
    self.password = nil
  end
end
  

Я не понимаю, почему пароль равен нулю при создании тестового объекта.

Комментарии:

1. В вашей модели должно быть несколько обратных вызовов, влияющих на способ вычисления пароля. Это видно из password_hash и password_salt заполняется. Можете ли вы показать свой код модели?

Ответ №1:

У вас есть требование о наличии пароля в вашей модели, но затем у вас есть after_save перехват, который аннулирует пароль и переводит запись в недопустимое состояние. Первый тест проходит, потому что ваши записи всегда переводятся в недопустимое состояние с помощью after_save перехвата. Вам нужно переосмыслить, как вы обрабатываете хранилище паролей; как только вы решите эту проблему, вот несколько примеров кода, которые помогут вам несколько способов проверить это:

 # Set up a :user factory in spec/factories.rb; it should look something like:
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "tester #{n}@gmail.com" }
    password         { SecureRandom.hex(6) }
  end
end 

# In your spec:
let(:user) { create :user, password: password }

context 'password' do
  context 'length < 6' do
    let(:password) { '12345' } 


    it { expect(user).not_to be_valid }
    it { user.errors.message[:password]).to include('something') }
  end 

  context 'length >= 6' do
    context 'length < 10' do
      let(:password) { 'blahblah' }

      it { expect(user).to be_valid }
    end

    context 'length >= 10' do
      let(:password) { 'blahblahblah' }

      it { expect(user).not_to be_valid }
    end
  end
end
  

Вы также можете использовать средства сопоставления shoulda:

 it { should_not allow_value('12345').for(:password) }
it { should allow_value('12345blah').for(:password) }
  

Ответ №2:

Наиболее вероятная проблема заключается password в том, что поле не может быть массово назначено. Вот почему password в выходном сообщении равно нулю.

Попробуйте это вместо:

 it "validation allows passwords larger than 6 and less than 10" do
  good_password = User.create(email: "tester2@gmail.com")
  good_password.password = "blahblah"
  expect(good_password).to be_valid
end
  

Обратите внимание, что ваш первый тест проходит случайно — у него та же проблема, что и у второго теста (пароль не присваивается). Это означает, что вы на самом деле не проверяете, что пароль отклоняется, когда в банкомате меньше 6 символов.

Более подробную информацию см. В этой статье о массовом назначении.

РЕДАКТИРОВАТЬ: комментарий Лео Корреа может предполагать, что это может быть не так для вас. Публикация вашего кода модели помогла бы…

Комментарии:

1. прошло 5 лет… Массовое присвоение было перенесено на уровень контроллера в Rails 4. Ничто (по умолчанию) не должно препятствовать присвоению любого поля в модели.