Выполнить перед блоком один раз с RSpec 3 - PullRequest
1 голос
/ 29 сентября 2019

Я не могу найти способ обойти это.

Это мой тест:

require 'rails_helper'

RSpec.describe V1::UsersController do

  describe '#create' do
    let(:post_params) do
      {
        first_nm: Faker::Name.first_name,
        last_nm: Faker::Name.last_name ,
        password: "test123456",
        password_confirmation: "test123456",
        email_address: Faker::Internet.email
      }
    end

    before do
      post :create, params: post_params
    end

    context 'successful create' do
      subject(:user) { User.find_by_email(post_params[:email_address]) }

      it 'persists the user' do
        expect(user).not_to be_nil
      end

      it 'user data is correct' do
        post_params.except(:password, :password_confirmation).each do |k, v|
          expect(user.send(k)).to eq(v)
        end
      end

      it 'returns responsde code of 201' do
        expect(response.status).to eq(201)
      end

    end
  end
end

Я хочу, чтобы этот контроллер был поражен только один раз. Однако я не могу заставить это работать.

Я попытался установить before(:context), и у меня появляется ошибка

RuntimeError:
       let declaration `post_params` accessed in a `before(:context)` hook at:

       `let` and `subject` declarations are not intended to be called
       in a `before(:context)` hook, as they exist to define state that
       is reset between each example, while `before(:context)` exists to
       define state that is shared across examples in an example group.

Я не хочу, чтобы несколько пользователей сохранялисьдля такого простого теста. Я также не хочу ударять по API для каждого примера.

Я хочу, чтобы блок before запускался один раз. Как я могу это сделать?

Ответы [ 2 ]

1 голос
/ 30 сентября 2019

Как указано в сообщениях об ошибках, let и subject специально предназначены для управления состоянием для каждого примера. Но хуки before(:context) / before(:all) запускаются за пределами какого-либо конкретного примера, поэтому они принципиально несовместимы. Если вы хотите использовать before(:context), вы не можете ссылаться на любые определения let из ловушки. Вам придется самостоятельно управлять состоянием post_params, не используя let. Вот простой способ сделать это:

require 'rails_helper'

RSpec.describe V1::UsersController do
  describe '#create' do
    before(:context) do
      @post_params = {
        first_nm: Faker::Name.first_name,
        last_nm: Faker::Name.last_name ,
        password: "test123456",
        password_confirmation: "test123456",
        email_address: Faker::Internet.email
      }

      post :create, params: @post_params
    end

    context 'successful create' do
      subject(:user) { User.find_by_email(@post_params[:email_address]) }

      it 'persists the user' do
        expect(user).not_to be_nil
      end

      it 'user data is correct' do
        @post_params.except(:password, :password_confirmation).each do |k, v|
          expect(user.send(k)).to eq(v)
        end
      end

      it 'returns responsde code of 201' do
        expect(response.status).to eq(201)
      end
    end
  end
end

Это должно решить вашу проблему;однако это не тот подход, который я бы рекомендовал. Вместо этого я рекомендую использовать функцию aggregate_failures в RSpec 3.3+ и поместить все это в один пример, например, так:

require 'rails_helper'

RSpec.describe V1::UsersController do
  describe '#create' do
    let(:post_params) do
      {
        first_nm: Faker::Name.first_name,
        last_nm: Faker::Name.last_name ,
        password: "test123456",
        password_confirmation: "test123456",
        email_address: Faker::Internet.email
      }
    end

    it 'successfully creates a user with the requested params', :aggregate_failures do
      post :create, params: post_params

      expect(response.status).to eq(201)
      user = User.find_by_email(post_params[:email_address])
      expect(user).not_to be_nil

      post_params.except(:password, :password_confirmation).each do |k, v|
        expect(user.send(k)).to eq(v)
      end
    end
  end
end

aggregate_failures дает сбойотчет с указанием каждого неудавшегося ожидания (а не только первого, как обычно), точно так же, как если бы вы разделили его на 3 отдельных примера, позволяя при этом фактически сделать его одним примером. Это позволяет вам инкапсулировать тестируемое действие в одном примере, позволяя выполнять действие только один раз, как вы хотите. Во многих отношениях это лучше согласуется с изолированной программной средой состояния для каждого примера, предоставляемой функциями RSpec, такими как before hooks, объявления let и откат DB-транзакции, предоставляемый rspec-rails. И

Мне настолько нравится функция aggregate_failures, что я склонен настраивать RSpec для автоматического применения ее к каждому примеру в spec_helper.rb:

RSpec.configure do |c|
  c.define_derived_metadata do |meta|
    meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
  end
end
0 голосов
/ 29 сентября 2019

То, что вы ищете, это before(:all), который будет запущен один раз перед всеми делами. Аналогично after(:all).

Интересно, что before - это, по сути, более короткий способ сказать before(:each) (что в ИМО имеет больше смысла).

...