Is it possible for RSpec to expect change in two tables? - ruby

RSpec expect change:
it "should increment the count" do
expect{Foo.bar}.to change{Counter.count}.by 1
end
Is there a way to expect change in two tables?
expect{Foo.bar}.to change{Counter.count}.by 1
and change{AnotherCounter.count}.by 1

I prefer this syntax (rspec 3 or later):
it "should increment the counters" do
expect { Foo.bar }.to change { Counter, :count }.by(1).and \
change { AnotherCounter, :count }.by(1)
end
Yes, this are two assertions in one place, but because the block is executed just one time, it can speedup the tests.
EDIT: Added Backslash after the .and to avoid syntax error

I got syntax errors trying to use #MichaelJohnston's solution; this is the form that finally worked for me:
it "should increment the counters" do
expect { Foo.bar }.to change { Counter.count }.by(1)
.and change { AnotherCounter.count }.by(1)
end
I should mention I'm using ruby 2.2.2p95 - I don't know if this version has some subtle change in parsing that causes me to get errors, it doesn't appear that anyone else in this thread has had that problem.

This should be two tests. RSpec best practices call for one assertion per test.
describe "#bar" do
subject { lambda { Foo.bar } }
it { should change { Counter.count }.by 1 }
it { should change { AnotherCounter.count }.by 1 }
end

If you don't want to use the shorthand/context based approach suggested earlier, you can also do something like this but be warned it will run the expectation twice so it might not be appropriate for all tests.
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

Georg Ladermann's syntax is nicer but it doesn't work. The way to test for multiple value changes is by combining the values in arrays. Else, only the last change assertion will decide on the test.
Here is how I do it:
it "should increment the counters" do
expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1])
end
This works perfectecly with the '.to' function.

The best way I've found is to do it "manually":
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)
Not the most elegant solution but it works

After none of the proposed solutions proved to actually work, I accomplished this by adding a change_multiple matcher. This will only work for RSpec 3, and not 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
example of usage:
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
Making by_at_least and by_at_most work for change_multiple would require some additional work.

I'm ignoring the best practices for two reasons:
A set of my tests are regression tests, I want them to run fast, and
they break rarely. The advantage of having clarity about exactly
what is breaking isn't huge, and the slowdown of refactoring my code
so that it runs the same event multiple times is material to me.
I'm a bit lazy sometimes, and it's easier to not do that refactor
The way I'm doing this (when I need to do so) is to rely on the fact that my database starts empty, so I could then write:
foo.bar
expect(Counter.count).to eq(1)
expect(Anothercounter.count).to eq(1)
In some cases my database isn't empty, but I either know the before count, or I can explicitly test for the before count:
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)
Finally, if you have a lot of objects to check (I sometimes do) you can do this as:
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
If you have similar needs to me this will work, my only advice would be to do this with your eyes open - the other solutions proposed are more elegant but just have a couple of downsides in certain circumstances.

Related

Is there a way in RSpec to assert both number of calls and the list of arguments together?

I want to assert that a certain method is called exactly N times (no more, no less) with specific arguments with a specific order. Also I don't want to actually execute this method so first I stub it with allow().
Suppose I have this code:
class Foo
def self.hello_three_times
Foo.hello(1)
Foo.hello(2)
Foo.hello(3)
end
def self.hello(arg)
puts "hello #{arg}"
end
end
I want to test method hello_three_times that it actually calls hello three times with 1, 2, and 3 as arguments. (And I don't want to really call hello in tests because in reality it contains side effects and is slow.)
So, if I write this test
RSpec.describe Foo do
subject { Foo.hello_three_times }
it do
allow(Foo).to receive(:hello).and_return(true)
expect(Foo).to receive(:hello).with(1).once.ordered
expect(Foo).to receive(:hello).with(2).once.ordered
expect(Foo).to receive(:hello).with(3).once.ordered
subject
end
end
it passes but it doesn't guarantee there are no additional calls afterwards. For example, if there is a bug and method hello_three_times actually looks like this
def self.hello_three_times
Foo.hello(1)
Foo.hello(2)
Foo.hello(3)
Foo.hello(4)
end
the test would still be green.
If I try to combine it with exactly(3).times like this
RSpec.describe Foo do
subject { Foo.hello_three_times }
it do
allow(Foo).to receive(:hello).and_return(true)
expect(Foo).to receive(:hello).exactly(3).times
expect(Foo).to receive(:hello).with(1).once.ordered
expect(Foo).to receive(:hello).with(2).once.ordered
expect(Foo).to receive(:hello).with(3).once.ordered
subject
end
end
it fails because RSpec seems to be treating the calls as fulfilled after the first expect (probably in this case it works in such a way that it expects to have 3 calls first, and then 3 more calls individually, so 6 calls in total):
Failures:
1) Foo is expected to receive hello(3) 1 time
Failure/Error: expect(Foo).to receive(:hello).with(1).once.ordered
(Foo (class)).hello(1)
expected: 1 time with arguments: (1)
received: 0 times
Is there a way to combine such expectations so that it guarantees there are exactly 3 calls (no more, no less) with arguments being 1, 2, and 3 (ordered)?
Oh, I think I found the solution. I can use a block for that:
RSpec.describe Foo do
subject { Foo.hello_three_times }
let(:expected_arguments) do
[1, 2, 3]
end
it do
allow(Foo).to receive(:hello).and_return(true)
call_index = 0
expect(Foo).to receive(:hello).exactly(3).times do |argument|
expect(argument).to eq expected_arguments[call_index]
call_index += 1
end
subject
end
end
It gets the job done guaranteeing there are exactly 3 calls with correct arguments.
It doesn't look very pretty though (introducing that local variable call_index, ugh). Maybe there are prettier solutions out of the box?
You can loop through the expected values and call the expectation for each one like this. You can do the same thing for the allow to ensure that it is only being called with the args that you want, or just keep the allow as you had it to allow anything.
RSpec.describe Foo do
subject { Foo.hello_three_times }
let(:expected_args){ [1, 2, 3] }
before do
expected_args.each do |arg|
allow(Foo).to receive(:hello).with(arg).and_return(true)
end
end
it 'calls the method the expected times' do
expected_args.each do |arg|
expect(Foo).to receive(:hello).with(arg).once
subject
end
end
end

Ruby - Testing a method that calls itself in Minitest

I'm having trouble developing unit tests for a method that calls itself (a game loop) in Ruby using minitest. What I've attempted has been stubbing the method I'm trying to call in said game loop with my input. Here's the game loop:
#main game loop
def playRound
#draw board
#board.printBoard
#get input
playerInput = gets.chomp #returns user input without ending newline
#interpret input, quitting or beginning set selection for a player
case playerInput
when "q"
quit
when "a", "l"
set = getPlayerSet()
if(playerInput == "a")
player = 1
else
player = 2
end
when "h"
if #hintsEnabled
giveHint
playRound
else
puts "Hints are disabled"
playRound
end
else
puts "Input not recognized."
end
if(set != nil)
#have board test set
checkSet(set, player)
end
#check if player has quitted or there are no more valid sets
unless #quitted || #board.boardComplete
playRound
end
end
Much of it is ultimately irrelevant, all I'm trying to test is that this switch statement is calling the correct methods. Currently I'm trying to circumvent the loop by stubbing the called method to raise an error (which my test assers_raise's):
def test_playRound_a_input_triggers_getPlayerSet
#game.stub :getPlayerSet, raise(StandardError) do
assert_raises(StandardError) do
simulate_stdin("") {
#game.playRound
}
end
end
end
This approach does not seem to work, however, as Minitest is recording the results of the above test as an error with the message
E
Error:
TestGame#test_playRound_a_input_triggers_getPlayerSet:
StandardError: StandardError
test_game.rb:136:in `test_playRound_a_input_triggers_getPlayerSet'
If anyone has any advice or direction for me it would be massively appreciated as I can't tell what's going wrong
I'm not very familiar with minitest, but I expect you need to wrap the raise(exception) in a block, otherwise your test code is raising the exception immediately in your test (not as a result of the stubbed method being called).
Something like:
class CustomTestError < RuntimeError; end
def test_playRound_a_input_triggers_getPlayerSet
raise_error = -> { raise(CustomTestError) }
#game.stub(:getPlayerSet, raise_error) do
assert_raises(CustomTestError) do
simulate_stdin("") {
#game.playRound
}
end
end
end
-- EDIT --
Sometimes when i'm having difficulty testing a method it's a sign that I should refactor things to be easier to test (and thus have a cleaner, simpler interface, possibly be easier to understand later).
I don't code games and don't know what's typical for a game loop, but that method looks very difficult to test. I'd try to break it into a couple steps where each step/command can be easily tested in isolation. One option for this would be to define a method for each command and use send. This would allow you to test that each command works separately from your input parsing and separately from the game loop itself.
COMMANDS = {
q: :quit,
# etc..
}.stringify_keys.freeze
def play_round # Ruby methods should be snake_case rather than camelCase
#board.print_board
run_command(gets.chomp)
play_round unless #quitted || #board.board_complete
end
def run_command(input)
command = parse_input_to_command(input)
run_command(command)
end
def parse_input_to_command(input)
COMMANDS[input] || :bad_command
end
def run_command(command)
send("run_#{command}")
end
# Then a method for each command, e.g.
def run_bad_input
puts "Input not recognized"
end
However, for this type of problem I really like a functional approach, where each command is just a stateless function that you pass state into and get new state back. These could either mutate their input state (eww) or return a new copy of the board with updated state (yay!). Something like:
COMMANDS = {
# All state change must be done on board. To be a functional pattern, you should not mutate the board but return a new one. For this I invent a `.copy()` method that takes attributes to update as input.
q: -> {|board| board.copy(quitted: true) },
h: -> HintGiver.new, # If these commands were complex, they could live in a separate class entirely.
bad_command: -> {|board| puts "Unrecognized command"; board },
#
}.stringify_keys.freeze
def play_round
#board.print_board
command = parse_input_to_command(gets.chomp)
#board = command.call(#board)
play_round unless #board.quitted || #board.board_complete
end
def parse_input_to_command(input)
COMMANDS[input] || COMMANDS[:bad_command]
end

return early vs if in ruby code

I see two styles of writing the same thing:
def find_nest(animal)
return unless animal.bird?
GPS.find_nest(animal.do_crazy_stuff)
end
vs
def find_nest(animal)
if animal.bird?
GPS.find_nest(animal.do_crazy_stuff)
end
end
Which one is more correct/preferable/following-best-practises? Or it does not matter?
As per Ruby style guide,
Prefer a guard clause when you can assert invalid data. A guard clause
is a conditional statement at the top of a function that bails out as
soon as it can.
# bad
def compute_thing(thing)
if thing[:foo]
update_with_bar(thing)
if thing[:foo][:bar]
partial_compute(thing)
else
re_compute(thing)
end
end
end
# good
def compute_thing(thing)
return unless thing[:foo]
update_with_bar(thing[:foo])
return re_compute(thing) unless thing[:foo][:bar]
partial_compute(thing)
end
It is obviously a matter of personal preference. But I prefer the early return. Not only does it make code "flatter" and easier to read, it also scales well with the number of checks. For example:
def create_terms_of_service_notification
return if Rails.env.test?
return if current_user.accepted_tos?
# imagine 5 more checks here.
# Now imagine them as a mess of nested ifs.
# create the notification
end
This :}
def find_nest(animal)
GPS.find_nest(animal.do_crazy_stuff) if animal.bird?
end

Why isn't rspec memoizing this?

I have a test which is a bit like the following. The details isn't important, but I have a method which takes about 10 seconds, and gets back some data which I want to use a bunch of times in a bunch of tests. The data won't be any more fresh - I only need to fetch it once. My understanding of let is that it memoizes, so I would expect the following to only call slow_thing once. But I see it called as many times as I refer to slowthing. What am I doing wrong?
describe 'example' do
def slow_thing
puts "CALLING ME!"
sleep(100)
end
let(:slowthing) { slow_thing }
it 'does something slow' do
expect(slowthing).to be_true
end
it 'does another slow thing' do
expect(slowthing).to be_true
end
end
When I run the test, I see CALLING ME! as many times as I have assertions or use slowthing.
The documentation states values are not cached across examples:
The value will be cached across multiple calls in the same example but not across examples. [Emphasis mine.]
E.g., also from the docs:
$count = 0
describe "let" do
let(:count) { $count += 1 }
it "memoizes the value" do
count.should == 1
count.should == 1
end
it "is not cached across examples" do
count.should == 2
end
end
From https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let

How would you express an idiom "with this object, if it exists, do this" in Ruby?

Very often in Ruby (and Rails specifically) you have to check if something exists and then perform an action on it, for example:
if #objects.any?
puts "We have these objects:"
#objects.each { |o| puts "hello: #{o}"
end
This is as short as it gets and all is good, but what if you have #objects.some_association.something.hit_database.process instead of #objects? I would have to repeat it second time inside the if expression and what if I don't know the implementation details and the method calls are expensive?
The obvious choice is to create a variable and then test it and then process it, but then you have to come up with a variable name (ugh) and it will also hang around in memory until the end of the scope.
Why not something like this:
#objects.some_association.something.hit_database.process.with :any? do |objects|
puts "We have these objects:"
objects.each { ... }
end
How would you do this?
Note that there's no reason to check that an array has at least one element with any? if you're only going to send each, because sending each to an empty array is a no-op.
To answer your question, perhaps you are looking for https://github.com/raganwald/andand?
Indeed, using a variable pollutes the namespace, but still, I think if (var = value).predicate is is a pretty common idiom and usually is perfectly ok:
if (objects = #objects.some_association.hit_database).present?
puts "We have these objects: #{objects}"
end
Option 2: if you like to create your own abstractions in a declarative fashion, that's also possible using a block:
#objects.some_association.hit_database.as(:if => :present?) do |objects|
puts "We have these objects: #{objects}"
end
Writing Object#as(options = {}) is pretty straigthforward.
What about tap?
#objects.some_association.something.hit_database.process.tap do |objects|
if objects.any?
puts "We have these objects:"
objects.each { ... }
end
end
Edit: If you're using Ruby 1.9, the Object#tap method provides the same functionality as the code listed below.
It sounds like you just want to be able to save a reference to an object without polluting the scope, correct? How about we open up the Object class and add a method do, which will just yield itself to the block:
class Object
def do
yield self if block_given?
return self # allow chaining
end
end
We can then call, for example:
[1,2,3].do { |a| puts a.length if a.any? }
=> 3
[].do { |a| puts a.length if a.any? }
=> nil

Resources