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 синтаксис.

Ако например тествате обхвати на модел, тогава спецификация, която изтрива DB, ще ви даде малка или никаква стойност.

Забиването може да ви даде бърз набор от тестове, който е изключително крехък поради тясното свързване и който позволява на много грешки да се промъкнат през пукнатините.

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