Ответ 1
Это должно быть два теста. Лучшие практики RSpec вызывают одно утверждение за тест.
describe "#bar" do
subject { lambda { Foo.bar } }
it { should change { Counter.count }.by 1 }
it { should change { AnotherCounter.count }.by 1 }
end
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
Это должно быть два теста. Лучшие практики RSpec вызывают одно утверждение за тест.
describe "#bar" do
subject { lambda { Foo.bar } }
it { should change { Counter.count }.by 1 }
it { should change { AnotherCounter.count }.by 1 }
end
Я предпочитаю этот синтаксис (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
, чтобы избежать ошибки синтаксиса
Я получил синтаксические ошибки, пытаясь использовать решение @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 - я не знаю, имеет ли эта версия некоторые тонкие изменения в синтаксическом анализе, которые заставляют меня получать ошибки, не кажется, что кто-либо из этого потока имел это проблема.
Если вы не хотите использовать предложенный ранее подход на основе сокращенного/контекстного подхода, вы также можете сделать что-то вроде этого, но будьте осторожны, он будет запускать ожидание дважды, чтобы он не был подходящим для всех тестов.
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
Лучший способ, который я нашел, - это "вручную":
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)
Не самое элегантное решение, но оно работает
Синтаксис Георга Ладермана лучше, но он не работает. Способ проверки нескольких изменений значений заключается в объединении значений в массивах. Иначе, только последнее утверждение изменения решит тест.
Вот как я это делаю:
it "should increment the counters" do
expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1])
end
Это отлично работает с функцией ".to".
Я игнорирую лучшие практики по двум причинам:
То, как я это делаю (когда мне это нужно), заключается в том, чтобы полагаться на то, что моя база данных пуста, поэтому я мог бы написать:
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
Если у вас есть схожие потребности, это будет работать, мой единственный совет - сделать это с открытыми глазами - другие предлагаемые решения более элегантны, но в определенных обстоятельствах есть только несколько недостатков.
После того, как ни одно из предлагаемых решений не доказало, что оно действительно работает, я выполнил это, добавив 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 потребует дополнительной работы.