Rails 5 получает Rspec с помощью ActionController::Params

Я только что обновился до Rails 5. В моих спецификациях у меня есть следующее

expect(model).to receive(:update).with(foo: 'bar')

Но, поскольку params больше не расширяет Hash, а теперь является ActionController::Parameters, спецификации терпят неудачу, потому что with() ожидает хэш, а на самом деле ActionController::Parameters

Есть ли лучший способ сделать то же самое в Rspec, например, другой метод with_hash?

Я могу обойти проблему, используя

expect(model).to receive(:update).with(hash_including(foo: 'bar'))

Но это просто проверка того, включает ли параметры этот хеш, а не проверка на точное совпадение.


person Carl Markham    schedule 26.09.2016    source источник


Ответы (3)


Вы можете сделать:

params = ActionController::Parameters.new(foo: 'bar')
expect(model).to receive(:update).with(params)

Однако это все еще пахнет - вы должны тестировать поведение приложения, а не то, как оно выполняет свою работу.

expect {
  patch model_path(model), params: { foo: 'bar' }
  model.reload
}.to change(model, :foo).to('bar')

Вот как я бы проверил интеграцию контроллера:

require 'rails_helper'
RSpec.describe "Things", type: :request do
  describe "PATCH /things/:id" do

    let!(:thing) { create(:thing) }
    let(:action) do
      patch things_path(thing), params: { thing: attributes }
    end

    context "with invalid params" do
      let(:attributes) { { name: '' } }
      it "does not alter the thing" do
         expect do 
           action 
           thing.reload
         end.to_not change(thing, :name)
         expect(response).to have_status :bad_entity
      end
    end

    context "with valid params" do
      let(:attributes) { { name: 'Foo' } }
       it "updates the thing" do
         expect do 
           action 
           thing.reload
         end.to change(thing, :name).to('Foo')
         expect(response).to be_successful
      end
    end
  end
end

Является ли прикосновение к базе данных в спецификации изначально плохим?

Нет. Когда вы тестируете что-то вроде контроллера, самый точный способ протестировать его — запустить полный стек. Если бы мы в этом случае заглушили @thing.update, мы могли бы пропустить, например, то, что драйвер базы данных выдал ошибку, потому что мы использовали неправильный синтаксис SQL.

Если вы, например, тестируете области на модели, то спецификация, которая заглушает БД, не даст вам практически никакой ценности.

Заглушка может дать вам быстрый тестовый набор, который чрезвычайно ненадежен из-за жесткой связи и позволяет множеству ошибок проскользнуть сквозь щели.

person max    schedule 26.09.2016
comment
Я всегда думал, что попадание в базу данных в рамках спецификаций — это плохо? Вот почему я всегда заглушаю метод на модели, а затем возвращаю двойное значение, если мне нужно проверить valid? и тому подобное. Таким образом, я могу ожидать, что двойник получит valid? и вернет true или false в зависимости от того, что я хочу проверить (успех или неудача). - person Carl Markham; 26.09.2016
comment
Это своего рода преувеличение — заглушка базы данных может быть хорошей идеей в модульных тестах, чтобы ускорить ваш набор тестов. Но когда вы тестируете что-то вроде контроллера, на самом деле вы выполняете функциональное или интеграционное тестирование, и все эти заглушки разрушают остроту ваших тестов. - person max; 26.09.2016
comment
DHH написал довольно интересный пост в блоге на предмет, который некоторое время назад сумел пощекотать многих людей. - person max; 26.09.2016
comment
@max Я не буду рекомендовать никому, кто хочет стать хорошим разработчиком, следовать каким-либо статьям dhh или его коду. - person Rustam A. Gasanov; 26.09.2016
comment
Никаких тебе рельсов тогда @RustamA.Gasanov. - person max; 26.09.2016
comment
Я согласен с тем, чтобы не заглушать модели. Но если мне нужно протестировать (служебный) объект, который вызывается контроллером, и в зависимости от ввода он выполняет множество действий, гораздо проще протестировать этот объект отдельно, а в контроллере проверить только то, что он проходит все, что нужно. нуждается в этом объекте. Спецификации функций обычно должны проверять, что все также хорошо интегрируется в базовый сценарий. - person trueunlessfalse; 05.11.2018
comment
@trueunlessfalse это другой сценарий, чем ваш типичный контроллер (и исходный вопрос), и он больше похож на то, когда вы имеете дело с чем-то вроде клиента API, который выходит за рамки приложения. Да, я согласен, что это место, где заглушки могут быть полезны. - person max; 30.01.2020

Я справился с этим, создав в spec/rails_helper.rb

def strong_params(wimpy_params)
  ActionController::Parameters.new(wimpy_params).permit!
end

а затем в конкретном тесте вы можете сказать:

expect(model).to receive(:update).with(strong_params foo: 'bar')

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

person Steve    schedule 02.08.2017

У @max были хорошие предложения о том, как этого вообще избежать, и я согласен, что они отказались от хэша, чтобы не поощрять их взаимозаменяемое использование с хэшами.

Однако, если вы все еще хотите использовать их в качестве простого хака для более сложных ситуаций (например, если вы expect используете a_hash_including), вы можете попробовать использовать что-то вроде этого:

  .with( an_object_satisfying { |o| 
           o.slice(some_params) == ActionController::Parameters.new(some_params)
         })
person maschwenk    schedule 19.10.2016
comment
Спасибо за ответ, я реализовал хак в спецификациях, пока выбираю лучший способ. Я в основном добавил метод to_params, который вызывает ActionController::Parameters.new hash, чтобы он преобразовывал хеш в спецификациях в ActionController::Params expect(foo).to receive(:bar).with(to_params(foo: 'bar')) - person Carl Markham; 19.10.2016
comment
вы пропатчили метод to_params для ActionController::Parameters ? - person maschwenk; 19.10.2016
comment
В примере в комментарии foo: 'bar' преобразуется в экземпляр ActionController::Parameters - person Carl Markham; 19.10.2016
comment
Ага, хорошо. Да запросто может это сделать. Я использовал описанную выше технику, потому что использовал a_hash_including, который не допускал такого простого преобразования. Ни один из них не является особенно идеальным. - person maschwenk; 19.10.2016