How can i test this method RSpec - ruby

I have this method - choose_option
enter image description here
and this help methods
enter image description here
Question, how can i test and take green light simpelcov.
allow(adapter).to receive(:gets).and_return('my_guess')
except(adapter).to receive(:adapter_my_guess)
But this don' work
Error:
expected: 1 time with any arguments
received: 2 times
Because my wrapper all time call choose_option seems like recursion
def adapter_my_guess(guess)
#game.instance_variable_set(:#secret_code, %w[1 2 3 4])
if #game.attempts.positive?
puts show_guess_result(#game.my_guess(guess))
else
puts I18n.t(:lose_game, secret_code: #game.instance_variable_get(:#secret_code))
end
end
def wrapper(method_for_wrap)
puts method_for_wrap
#game.win == false ? choose_option : Finishe.new(#game, #statistics).finishe
end
def choose_option
option = input(I18n.t(:start_game_options, option1: OPTION_1, option2: OPTION_2, exit: EXIT))
case option
when OPTION_1 then wrapper(adapter_my_guess(input(I18n.t(:puts_guess, exit: OR_EXIT))))
when OPTION_2 then wrapper(adapter_give_hints)
else
raise(StandardError, WRONG_OPTION)
end
rescue StandardError => e
puts e
choose_option
end

except(adapter).to receive(:adapter_my_guess) says adapter will receive one and only one call to adapter_my_guess. It's received two.
If that is correct, change your expectation to expect more calls by adding at_least(1).time.
If that is not correct, we'd need to know more about your code and test.
Some notes...
Do not use instance_variable_get and instance_variable_set in production code. Grabbing at other object's internal variables tangles up the code and makes it hard to change the object without unforeseen consequences. Make secret_code a proper accessor method.
choose_option does three things. It gets the option, it interprets the option, and it decides if it needs to get the option again. Split it into three methods, one to get the option, one to interpret the option, and one to put them together. That will be more flexible and easier to test.
Catching every StandardError is too broad. With the methods split, the option is an argument to a method. You can change the exception to be a more specific ArgumentError.
def get_option
input(I18n.t(:start_game_options, option1: OPTION_1, option2: OPTION_2, exit: EXIT))
end
def do_option(option)
case option
when OPTION_1 then wrapper(adapter_my_guess(input(I18n.t(:puts_guess, exit: OR_EXIT))))
when OPTION_2 then wrapper(adapter_give_hints)
else
raise(ArgumentError, WRONG_OPTION)
end
end
def choose_option
do_option(get_option)
rescue ArgumentError
choose_option
end
Now each can be unit tested without having to mock the whole process. For example, all we need to test in choose_option is whether it gets an option, tries to do something with it, and retries.
describe '#choose_option' do
context 'with a good option' do
it 'gets and does the option once' do
option = double
expect(adapter).to receive(:get_option)
.and_return(option)
expect(adapter).to receive(:do_option)
.with(double)
adapter.choose_option
end
end
context 'with a bad option' do
it 'gets and does the option again' do
good_option = double
bad_option = double
# This will raise an ArgumentError.
expect(adapter).to receive(:do_option)
.with(bad_option)
.and_call_original
# This will not.
expect(adapter).to receive(:do_option)
.with(good_option)
# First use the bad option, then the good one.
expect(adapter).to receive(:get_option)
.and_return(bad_option, good_option)
adapter.choose_option
end
end
end
We don't need to know what do_option nor get_option does to test choose_option, they can be totally mocked. do_option and get_option can be unit tested separately.
describe '#do_option' do
subject { adapter.do_option(option) }
context 'with option 1' do
let(:option) { described_class.OPTION_1 }
it 'guesses' do
expect(adapter).to receive(:adapter_my_guess)
subject
end
end
context 'with option 2' do
let(:option) { described_class.OPTION_2 }
it 'gives hints' do
expect(adapter).to receive(:adapter_give_hints)
subject
end
end
context 'with the wrong option' do
let(:option) { "basset hounds got long ears" }
it 'raises' do
expect { subject }.to raise_error(ArgumentError)
end
end
end
That's the basic approach.

Related

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

Can you simultaneously test a state change and a return value in RSpec?

Say I have a method MyKlass#do_thing that I want to call exactly once in a test (because it might change a state), and that should return true on successful state change and false otherwise. I want to write a spec that looks something like this:
it "Updates myvalue if condition is met" do
wojit = MyKlass.new
# ... assuming condition is met
expect { wojit.do_thing }.to change { wojit.value }.and.be true
end
But this particular approach gets an ArgumentError, because #and expects 1 argument.
I can make it work with the following abomination:
expect { expect(wojit.do_thing).to be true }.to change { wojit.value }
But that is hiiiiideous. Am I missing something more idiomatic?
Another approach is just to stick the return value in a variable.
return_value = nil
expect{ return_value = wojit.do_thing }.to change{ wojit.value }
expect( return_value ).to be true
YMMV as to whether it's better or worse than nested expects.
Maybe is not what you are looking for, but I actually think that "something more idiomatic" would be to make to tests using a describe or context block to express better that you testing the same case.
describe "When condition is met" do
it "updates the value" do
wojit = Wojit.new
expect { wojit.do_thing }.to change { wojit.value }
end
it "returns true" do
wojit = Wojit.new
expect(wojit.do_thing).to be_true
end
end
You could implement your own custom Matcher for this specific case like:
RSpec::Matchers.define :respond_with do |expected|
match do |actual|
actual.call == expected
end
# allow the matcher to support block expectations
supports_block_expectations
# make sure this executes in the correct context
def expects_call_stack_jump?
true
end
end
Then your expectation would be something like
it "Updates myvalue if condition is met" do
wojit = MyKlass.new
expect{wojit.do_thing}.to change(wojit, :value).and(respond_with(true))
end
The key here is that be,eq, etc. does not support block expectations and thus cannot be used in conjuction with expect{...} so we implemented an equality matcher that does support block expectations (supports_block_expectations? #=> true) and jumped it up the stack (this is very important in this case otherwise the change block creates a conflicting actual *Not sure I 100% understand why but trust me it does).
In this case actual will be the block body (as a Proc) so we just have to call it to compare the result to the expected value.
You could however abstract this out further to something like
RSpec::Matchers.define :have_response do |expectation|
supports_block_expectations
def expects_call_stack_jump?
true
end
#Actual matching logic
match do |actual|
#actual_value = actual.respond_to?(:call) ? actual.call : actual
expect(#actual_value).to(expectation)
end
failure_message do |actual|
"expected response to be #{expectation.expected} but response was #{#actual_value}"
end
failure_message_when_negated do |actual|
"expected response not to be #{expectation.expected} but response was #{#actual_value}"
end
end
#define negation for chaining purposes as needed
RSpec::Matchers.define_negated_matcher :not_have_response, :have_response
Which would allow you to use all the methods that do not support block expectations like so
it "Updates myvalue if condition is met" do
wojit = MyKlass.new
expect{wojit.do_thing}.to change(wojit, :value).and(have_response(be true))
# or
# expect{wojit.do_thing}.to not_have_response(be false).and(change(wojit, :value))
end
Only issue with either one of these approaches is that the block will be called once for the change and once for the response check so depending on your circumstances this could cause issues.

Ruby skips items from list tasks

I am trying to make an app which if give the option to type, it types false then it skips the certain element from the list and it jumps to the next executing the same task.
That is the basic idea of the following code:
string["items"].each do |item|
p continue.to_s + "<- item"
begin
Anemone.crawl("http://" + item["displayLink"] + "/") do |anemone|
anemone.on_every_page do |page|
if continue.chomp.to_bool == false
raise "no more please"
end
request = Typhoeus::Request.new(page.url, followlocation: true)
response = request.run
email = /[-0-9a-zA-Z.+_]+#[-0-9a-zA-Z.+_]+\.[a-zA-Z]{2,4}/.match(response.body)
if email.nil?
else
p email
begin
continue = Timeout::timeout(2) do
p "insert now false/nothing"
gets
end
rescue Timeout::Error
continue = "true"
end
end
end
end
rescue
continue = true
next
end
p "---------------------------------------------------------"
end
As the code shows, if the user types false when prompted the app should skip the item and go to the next one. However what it does is: when the user types false the app skips the current item and then doesn't execute any of the code that should be executed for all of the other items except the printing ( the second line of code );
Here is how the output looks like:
$ruby main.rb
"1"
"true<- item"
#<MatchData "support#keycreative.com">
"insert now false/nothing"
false
"true<- item"
"true<- item"
"true<- item"
As I'm doing my best to show after false is entered the code does skip the certain item from the list but it also never ever executes code for the other items as it should since it is an each loop
First I thought that maybe the continue is false however as you can see from the output the continue is true which makes me wonder why does ruby skip my code?
UPDATE
Here is where the to_bool method comes from:
class String
def to_bool()
return true if self == "true"
return false if self == "false"
return nil
end
end
In your last rescue statement add:
rescue => e
puts e.message
continue = true
next
end
and inspect the output. Most likely your code is throwing an exception other than "no more please" (I expect undefined method to_bool for true:TrueClass). Note that using exception for skipping the loop element is a terrible idea. Why can't you just get rid of this rescue and do:
if continue.chomp.to_bool == false
continue = true
next
end
There are a lot of things in this code which makes it very un-ruby-like. If you want to improve it please paste it to StackExchange CodeReview page. (link in the comment).
UPDATE:
My bad, you are in nested loop, so the if statement won't work. You might look at sth similar to raise/rescue bit, namely throw/catch, see example here: How to break from nested loops in Ruby?. I still think you should post it to codereview though for refactoring advises.
As to your actual code (without refactoring). You are calling to_bool method on continue, and in your rescue block you assign true instead of 'true'. Hence your to_bool method raises exception which is then rescued same way as 'no more please' exception.

How to write a nested search the Ruby way?

I am trying to write fast and concise code. I'd appreciate your thoughts on which is the best way to write the following code and why:
Option #1
def get_title
title = check_in_place_one
if title.empty?
title = check_in_place_two
if title.empty?
title = check_in_place_three
end
end
return title
end
Option #2
def get_title
title = check_in_place_one
title = check_in_place_two unless !title.empty?
title = check_in_place_three unless !title.empty?
return title
end
I think Option #1 is better since if the title is found by check_in_place_one, we test title.empty? once and then skip the rest of the code in the method and return. But, it looks too long. Option #2 appears better, but processes title.empty? one extra time, and unnecessary time before returning. Also, am I missing a third option?
From performance, there is no difference between the two versions of your code (besides very minor difference that may come from parsing, which should be ignorable). The control structures are the same.
From readability, if you can get away with nesting, doing so is better. Your second option is better.
It is usually better to get rid of any case that does not need further processing. That is done by return.
def get_title
title = check_in_place_one
return title unless title.empty?
title = check_in_place_two
return title unless title.empty?
title = check_in_place_three
return title
end
The last title = and return in the code above are redundant, but I put them there for consistency, which improves readability.
You can further compact the code using tap like this:
def get_title
check_in_place_one.tap{|s| return s unless s.empty?}
check_in_place_two.tap{|s| return s unless s.empty?}
check_in_place_three
end
tap is a pretty much fast method, and unlike instance_eval, its performance penalty is usually ignorable.
The following approach could be used for any number of sequential tests. Moreover, it is completely general. The return condition could be changed, arguments could easily be assigned to the test methods, etc.
tests = %w[check_in_place_one check_in_place_two check_in_place_three]
def do_tests(tests)
title = nil # Define title outside block
tests.each do |t|
title = send(t)
break unless title.empty?
end
title
end
Let's try it:
def check_in_place_one
puts "check 1"
[]
end
def check_in_place_two
puts "check 2"
''
end
def check_in_place_three
puts "check 3"
[3]
end
do_tests(tests) #=> [3]
check 1
check 2
check 3
#=> [3]
Now change one of the tests:
def check_in_place_two
puts "check 2"
'cat'
end
do_tests(tests) #=> 'cat'
check 1
check 2
#=> "cat"
If there were more tests, it might be convenient to put them in a module which would be included into a class. Mixed-in methods behave the same as those that you define for the class. For example, they have access to instance variables. I will demonstrate that with the definition of the first test method. We probably want to make the test methods private. We could do it like this:
module TestMethods
private
def check_in_place_one
puts "#pet => #{#pet}"
puts "check 1"
[]
end
def check_in_place_two
puts "check 2"
''
end
def check_in_place_three
puts "check 3"
[3]
end
end
class MyClass
##tests = TestMethods.private_instance_methods(false)
puts "##tests = #{##tests}"
def initialize
#pet = 'dog'
end
def do_tests
title = nil # Define title outside block
##tests.each do |t|
title = send(t)
break unless title.empty?
end
title
end
include TestMethods
end
The following is displayed when the code is parsed:
##tests = [:check_in_place_one, :check_in_place_two, :check_in_place_three]
Now we perform the tests:
MyClass.new.do_tests #=> [3]
#pet => dog
check 1
check 2
check 3
Confirm the test methods are private:
MyClass.new.check_in_place_one
#=> private method 'check_in_place_one' called for...'
The advantage of using a module is that you can add, delete, rearrange and rename the test methods without making any changes to the class.
Well, here's a few other alternatives.
Option 1: Return first non-empty check.
def get_title
return check_in_place_one unless check_in_place_one.empty?
return check_in_place_two unless check_in_place_two.empty?
return check_in_place_three
end
Option 2: Helper method with short-circuit evaluation.
def get_title
check_place("one") || check_place("two") || check_place("three")
end
private
def check_place(place)
result = send("check_in_place_#{place}")
result.empty? ? nil : result
end
Option 3: Check all places then find the first that's non-empty.
def get_title
[
check_in_place_one,
check_in_place_two,
check_in_place_three,
].find{|x| !x.empty? }
end
Option 2 looks good although you did a 360 degree turn with the unless !title.empty?. You can shorten that to if title.empty? since unless is equivalent to an if ! so doing an unless ! takes you back to just if.
If you're only ever going to have 3 places to look in then option 2 is the best. It's short, concise, and easy to read (easier once you fix the aforementioned whoopsie). If you might add on to the places you look for a title in you can get a bit more dynamic:
def get_title(num_places = 4)
title, cur_place = nil, 0
title = check_in_place(cur_place += 1) while title.nil? && cur_place < num_places
end
def check_in_place(place_num)
# some code to check in the place # given by place_num
end
The tricky line is that one with the while in it. What's happening is that the while will check the expression title.nil? && cur_place < num_places and return true because the title is still nil and 0 is less than 4.
Then we'll call the check_in_place method and pass in a value of 1 because the cur_place += 1 expression will increment its value to 1 and then return it, giving it to the method (assuming we want to start checking in place 1, of course).
This will repeat until either check_in_place returns a non nil value, or we run out of places to check.
Now the get_title method is shorter and will automatically support checking in num_places places given that your check_in_place method can also look in more places.
One more thing, you might like to give https://codereview.stackexchange.com/ a look, this question seems like it'd be a good fit for it.
I don't think there's any reason to get too clever:
def get_title
check_in_place_one || check_in_place_two || check_in_place_three
end
Edit: if the check_in_place_X methods are indeed returning an empty string on failure it would be better (and more idiomatic) to have them instead return nil. Not only does it allow for truthy comparisons like the above code, return "" results in the construction of a new and unnecessary String object.

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

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.

Resources