Подтвердить что ты не робот

Возможно ли, чтобы RSpec ожидал изменения в двух таблицах?

RSpec ожидает изменения:

it "should increment the count" do
  expect{Foo.bar}.to change{Counter.count}.by 1
end

Можно ли ожидать изменения в двух таблицах?

expect{Foo.bar}.to change{Counter.count}.by 1 
and change{AnotherCounter.count}.by 1 
4b9b3361

Ответ 2

Я предпочитаю этот синтаксис (rspec 3 или новее):

it "should increment the counters" do
  expect { Foo.bar }.to change { Counter,        :count }.by(1).and \
                        change { AnotherCounter, :count }.by(1)
end

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

EDIT: добавлена ​​обратная слэш после .and, чтобы избежать ошибки синтаксиса

Ответ 3

Я получил синтаксические ошибки, пытаясь использовать решение @MichaelJohnston; это форма, которая окончательно сработала для меня:

it "should increment the counters" do
  expect { Foo.bar }.to change { Counter.count }.by(1)
    .and change { AnotherCounter.count }.by(1)
end

Я должен упомянуть, что я использую ruby ​​2.2.2p95 - я не знаю, имеет ли эта версия некоторые тонкие изменения в синтаксическом анализе, которые заставляют меня получать ошибки, не кажется, что кто-либо из этого потока имел это проблема.

Ответ 4

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

it "should increment the count" do
  expectation = expect { Foo.bar }
  expectation.to change { Counter.count }.by 1
  expectation.to change { AnotherCounter.count }.by 1
end

Ответ 5

Лучший способ, который я нашел, - это "вручную":

counters_before         = Counter.count
another_counters_before = AnotherCounter.count
Foo.bar
expect(Counter.count).to eq (counters_before + 1)
expect(AnotherCounter.count).to eq (another_counters_before + 1)

Не самое элегантное решение, но оно работает

Ответ 6

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

Вот как я это делаю:

it "should increment the counters" do
  expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1])
end

Это отлично работает с функцией ".to".

Ответ 7

Я игнорирую лучшие практики по двум причинам:

  • Набор моих тестов - это регрессионные тесты, я хочу, чтобы они работали быстро, и они редко ломаются. Преимущество наличия четкости в точности то, что ломается, не огромно, и замедление рефакторинга моего кода так что он запускает одно и то же событие несколько раз для меня.
  • Иногда я немного ленив, и мне легче делать этот рефактор

То, как я это делаю (когда мне это нужно), заключается в том, чтобы полагаться на то, что моя база данных пуста, поэтому я мог бы написать:

foo.bar
expect(Counter.count).to eq(1)
expect(Anothercounter.count).to eq(1)

В некоторых случаях моя база данных не пуста, но я либо знаю счетчик до, либо я могу явно проверять счетчик до:

counter_before = Counter.count
another_counter_before = Anothercounter.count

foo.bar

expect(Counter.count - counter_before).to eq(1)
expect(Anothercounter.count - another_counter_before).to eq(1)

Наконец, если у вас есть много объектов для проверки (иногда я это делаю), вы можете сделать это как:

before_counts = {}
[Counter, Anothercounter].each do |classname|
  before_counts[classname.name] = classname.count
end

foo.bar

[Counter, Anothercounter].each do |classname|
  expect(classname.count - before_counts[classname.name]).to be > 0
end

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

Ответ 8

После того, как ни одно из предлагаемых решений не доказало, что оно действительно работает, я выполнил это, добавив change_multiple. Это будет работать только для RSpec 3, а не для 2. *

module RSpec
  module Matchers
    def change_multiple(receiver=nil, message=nil, &block)
      BuiltIn::ChangeMultiple.new(receiver, message, &block)
    end
    alias_matcher :a_block_changing_multiple,  :change_multiple
    alias_matcher :changing_multiple,          :change_multiple

    module BuiltIn
      class ChangeMultiple < Change
        private

          def initialize(receiver=nil, message=nil, &block)
            @change_details = ChangeMultipleDetails.new(receiver, message, &block)
          end
      end
      class ChangeMultipleDetails < ChangeDetails
        def actual_delta
          @actual_after = [@actual_after].flatten
          @actual_before = [@actual_before].flatten
          @actual_after.map.with_index{|v, i| v - @actual_before[i]}
        end
      end
    end
  end
end

пример использования:

it "expects multiple changes despite hordes of cargo cultists chanting aphorisms" do
  a = "." * 4
  b = "." * 10
  times_called = 0
  expect {
    times_called += 1
    a += ".."
    b += "-----"
  }.to change_multiple{[a.length, b.length]}.by([2,5])
  expect(times_called).to eq(1)
end

Выполнение by_at_least и by_at_most работы для change_multiple потребует дополнительной работы.