RSpec - how to test if a different class' method was called? - ruby

I want to monkey patch a String#select method, and want to create a test suite that checks that it doesn't use Array#select.
I tried creating a bunch of tests using to_not receive, using both to_not receive(Array:select) and just to_not receive(:select). I also tried using an array (string.chars) instead of the string . Google and stack overflow did not bring an answer.
describe "String#select" do
it "should not use built-in Array#select" do
string = "HELLOworld".chars
expect(string).to_not receive(Array:select)
end
end
Expected: a working test suite that checks that Array#method has not been used in the whole method.
Actual output: I'm getting an error that not enough arguments have been used. Output log below:
1) RECAP EXERCISE 3 Proc Problems: String#select should not use built-in Array#select
Failure/Error: expect(string).to_not receive(Array:select)
ArgumentError:
wrong number of arguments (given 0, expected 1..4)
# ./spec/problems_spec.rb:166:in `select'
# ./spec/problems_spec.rb:166:in `block (4 levels) in <top (required)>'

First of all: tests are supposed to check the results of methods called, not the way they are implemented. Relying to much on this would get you in trouble.
But there might be a legit reasons to do it, but think hard of you can test it other way:
Let's say that String#select uses Array#select internally, and the latter is buggy under some circumstances. It's better to make a test, setting up the universe in a way that would trigger the bug and check that the buggy behavior is not present . Then patch the String#select and have the test green. It's much better approach, because the test now tells everyone why you're not supposed to use Array#select internally. And if the bug is removed, it's the easiest thing under the sun to remove patch and check if the spec is still green.
That being said, if you still need that you can use expect_any_instance_of to acomplish that, for example this spec would fail:
class String
def select(&block)
split.select(&block) # remove this to make the spec pass
end
end
specify do
expect_any_instance_of(Array).not_to receive(:select)
'foo'.select
end
If you don't want to use expect_any_instance_of (because reasons), you can temporarily overwrite a method in a class to fail:
class String
def select(&block)
#split.select(&block)
end
end
before do
class Array
alias :backup_select :select
def select(*)
raise 'No'
end
end
end
after do
class Array
alias :select :backup_select # bring the original implementation back
end
end
specify do
expect { 'foo'.select }.not_to raise_error
end
Aliasing is needed to bring back the original implementation, so you don't mess up the specs that are run after this one.
But you can see how involved and messy this approach is.
Anyway - what you're trying to achieve is most probably a design issue, but it's hard to tell without more details.

Related

RSpec: Using a method stub to test interactive user input

I'm new to both Ruby and RSpec, and have already spent hours trying to write the first steps for testing an interactive tic tac toe program, but haven't located any useful answers to the error I'm getting:
> bundle exec rspec --format documentation
Do you want to play tic-tac-toe? (y/n)
An error occurred while loading ./spec/tic_tac_toe_spec.rb.
Failure/Error: choice = gets.chomp.downcase
Errno::ENOENT:
No such file or directory # rb_sysopen - --format
# ./lib/tic_tac_toe.rb:70:in `gets'
# ./lib/tic_tac_toe.rb:70:in `gets'
# ./lib/tic_tac_toe.rb:70:in `start_game'
# ./lib/tic_tac_toe.rb:144:in `<top (required)>'
# ./spec/tic_tac_toe_spec.rb:1:in `require'
# ./spec/tic_tac_toe_spec.rb:1:in `<top (required)>'
No examples found.
Finished in 0.0004 seconds (files took 0.08415 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples
When you start the game, it prompts whether you want to play (y/n). I've already attempted different variations of using mocking and stubs but always get this same error. I imagine I'm missing something very basic but cannot see it.
Question: how would one adequately address (using RSpec) this first prompt of the game?
The game uses an independent method (start_game) but not a class to start. I'm looking for some way mock or stub this with some default input (like y or n) as I want the tests to be automated. All the examples I see use a class to start a program, however (rather than a stand-alone method like here).
Currently in spec/tic_tac_toe_spec.rb there are some let keywords for :output and :game which I found from an example somewhere but obviously this doesn't work.
Edit I want to test the following method. The RSpec code keeps choking on the choice = gets.chomp.downcase line.
def start_game
puts "Do you want to play tic-tac-toe? (y/n)"
choice = gets.chomp.downcase
unless ["y","n"].include?(choice)
abort("Please answer with y or n.")
end
case choice
when "y"
puts "Ok, let's start!"
b = Board.new
puts "Enter name of player 1 (O)"
player1name = gets.chomp
player1 = Player.new(player1name)
puts "Now enter name of player 2 (X)"
player2name = gets.chomp
player2 = Player.new(player2name)
play_game(player1, player2, b)
when "n"
puts "Your loss."
end
end #start_game
You are receiving the error because you are passing the --format documentation as a parameter to RSpec.
gets reads the ARGV that have been passed. This also includes the RSpec parameters. You should use STDIN.gets so that you only read standard input and not the parameters.
You can read more about in this question:
https://stackoverflow.com/a/19799223/6156030
A simple approach could be:
it 'works' do
allow($stdin).to receive(:gets).and_return('y')
expect { start_game } # (Or however you run it!)
.to output("Ok, let's start!")
.to_stdout
end
You can also specify multiple return values for gets, which will be used sequentially as the test runs.
The alternative approach you seem to have started (but not fully implemented) is to inject an explicit output (and, presumably, one day an input) object into the game.
This would indeed be a cleaner approach from the purist's point of view - i.e. not stubbing the global objects like $stdin - but is probably overkill for your initial version of the code. Unless you plan doing something fancy like running parallel specs, I wouldn't worry about that.
Edit: After looking at your actual code in more detail, I see the problem. You're defining global methods which are doing multiple things and are tightly coupled. This makes writing tests much harder!
Here I have added a working test example:
https://github.com/tom-lord/tic_tac_toe_rspec/commit/840df0b7f1380296db97feff0cd3ca995c5c6ee3
However, going forward in order to simplify this my advice would be to define all each method within an appropriate class, and make the code less procedural. (i.e. Don't just make the end of one method call the next method, in a long sequence!) This refactoring is perhaps beyond the scope of a StackOverflow answer though.
You need to stub and mock gets method in your specs:
yes_gets = double("yes_gets")
allow($stdin).to receive(:gets).and_return(yes_gets)
Which then you can make it respond to #chomp:
expect(yes_gets).to receive(:chomp).and_return('Y')
You can cover the similar method call for downcase by returning this double object itself.
You can also do the similar work for mock object for your 'N' case where you'd expect game to exit when player inputs an N(No):
no_gets = double("no_gets")
allow($stdin).to receive(:gets).and_return(no_gets)
expect(no_gets).to receive(:chomp).and_return('N')

Can variables be passed after a do/end block?

I am working with a custom testing framework and we are trying to expand some of the assert functionality to include a custom error message if the assert fails. The current assert is called like this:
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end
and we want something with the functionality of:
assert_compare(first_term, :neq, second_term, error_message) do
puts 'foobar'
end
so that if the block fails the error message will describe the failure. I think this is ugly, however, as the framework we are moving away from did this and i have to go through a lot of statements that look like:
assert.compare(variable_foo['ARRAY1'][2], variable_bar['ARRAY2'][2], 'This assert failed because someone did something unintelligent when writing the test. Probably me, since in am the one writing this really really long error statement on the same line so that you have to spend a quarter of your day scrolling to the side just to read it')
This type of method call makes it difficult to read, even when using a variable for the error message. I feel like a better way should be possible.
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end on_fail: 'This is a nice error message'
This, to me, is the best way to do it but i don't know how or if it is even possible to accomplish this in ruby.
The goal here is to make it as aesthetic as possible. Any suggestions?
You could make on_fail a method of whatever assert_compare returns and write
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end.on_fail: 'This is a nice error message'
In short, no. Methods in ruby take a block as the final parameter only. As Chuck mentioned you could attempt to make the on_fail method a method of whatever assert_compare returns and that is a good solution. The solution I've come up with is not what you are looking for, but it works:
def test block, param
block.call
puts param
end
test proc { puts "hello"}, "hi"
will result in
"hello"
"hi"
What I've done here is create a Proc (which is essentially a block) and then passed it as a regular parameter.

Beginner RSpec: Need help writing Ruby code to pass RSpec tests (Silly Blocks exercise)

The following is a section of exercise #5 (Silly Blocks) from Test-First.org which I'm trying to crack as I learn on my own, in preparation for a Ruby class.
Each exercise comes with an RSpec '_spec.rb' file and the user is expected to write a corresponding Ruby code '.rb' file, and continue to "rake it" until all the RSpec tests (examples) within are satisfied. At least that is my interpretation and I've been able to get through the first four exercises, however, the RSpec syntax in this exercise has me stumped. (Unfortunately, I'm not only fairly new to coding, I'm definitely very new to RSpes and I haven't been able to find a good newbie-grade intro to RSpec/TDD online).
Thus, I'm hoping a resident RSpec expert might help. Basically, I'd like to know what exactly is the following RSpec syntax telling me to write for code?
require "silly_blocks"
describe "some silly block functions" do
describe "reverser" do
it "reverses the string returned by the default block" do
result = reverser do
"hello"
end
result.should == "olleh"
end
...
I assumed that I'm to write a method called 'reverser' which accepts a string argument, and returns the sting reversed, such as:
def reverser(string)
return string.reverse
end
Alas, that is clearly not correct - the rake fails miserably:
some silly block functions
reverser
reverses the string returned by the default block (FAILED - 1)
Failures:
1) some silly block functions reverser reverses the string returned by the def
ault block
Failure/Error: result = reverser do
ArgumentError:
wrong number of arguments (0 for 1)
# ./05_silly_blocks/silly_blocks.rb:3:in `reverser'
# ./05_silly_blocks/silly_blocks_spec.rb:15:in `block (3 levels) in <top (r
equired)>'
I suspect it has something to do with passing "default code blocks" but I'm not sure how to structure that. There are many more methods to write in this exercise, however, if I can get some clarity on the initial one, I think I can work out the rest!
Major thanks, Danke sehr, Muchas gracias!! :)
As far as I know, since this method takes a block and does something with it, you need to define the method to take a block, rather than an argument. So to enable the method to do this:
reverser do
"hello"
end
You would write it something like:
def reverser
yield.reverse
end
or:
def reverser(&block)
block.call.reverse
end
Now the above methods will work when a block is passed to it: reverser { "hello" }, but not when an argument is used: reverser("hello").

When does Ruby know that a method exists?

One question that ran through my mind was how does the Ruby interpreter know that a method exists on a object if the definition is yet to be interpreted? Like, wouldn't it matter whether you define the method first than use it, rather than use it then define it?
It doesn't know, and it doesn't care - until execution. When a method call statement is executed, the interpreter looks to see if the class (object, not code!) has the named function. If it does not, it looks up the ancestor tree. If it does not find any, it calls the method_missing method. If that is not defined, you get your error.
If your function call does not get executed, you will not get any errors.
The interpreter doesn't know about undefined methods ahead of time, for example:
o = Object.new
o.foo # => Raises NoMethodError.
class Object
def foo
puts "Foo!"
end
end
o.foo # => prints "Foo!", since the method is defined.
However, Ruby has a neat feature called method_missing which let's the receiver of a method call take the method name and arguments as separate arguments and handle accordingly as long as no defined method already handles the call.
def o.method_missing(sym, *args)
puts "OK: #{sym}(#{args.inspect})"
# Do something depending on the value of 'sym' and args...
end
o.bar(1, 2, 3) #=> OK: bar(1, 2, 3)
"Method missing" is used by things like active record find methods and other places where it could make sense to have "dynamically defined" functions.
The problem is, the interpreter tried to find it when you use it, and since it won't be there, it may fail.
In ( some ) compiled languages, it doesn't matter, because while compiling, the compiler may say "I'll look for this on a second pass" but I don't think this is the case with Ruby.

Ruby Koan: test_nil_is_an_object

I have recently tried sharpening my rails skills with this tool:
http://github.com/edgecase/ruby_koans
but I am having trouble passing some tests. Also I am not sure if I'm doing some things correctly since the objective is just to pass the test, there are a lot of ways in passing it and I may be doing something that isn't up to standards.
Is there a way to confirm if I'm doing things right?
a specific example:
in about_nil,
def test_nil_is_an_object
assert_equal __, nil.is_a?(Object), "Unlike NULL in other languages"
end
so is it telling me to check if that second clause is equal to an object(so i can say nil is an object) or just put assert_equal true, nil.is_a?(Object) because the statement is true?
and the next test:
def test_you_dont_get_null_pointer_errors_when_calling_methods_on_nil
# What happens when you call a method that doesn't exist. The
# following begin/rescue/end code block captures the exception and
# make some assertions about it.
begin
nil.some_method_nil_doesnt_know_about
rescue Exception => ex
# What exception has been caught?
assert_equal __, ex.class
# What message was attached to the exception?
# (HINT: replace __ with part of the error message.)
assert_match(/__/, ex.message)
end
end
Im guessing I should put a "No method error" string in the assert_match, but what about the assert_equal?
assert_equal true, nil.is_a?(Object) is indeed the correct solution. The question is "Are nils in Ruby objects or not?", and in Ruby's case, they are. Thus, in order to pass the assertion, you should assert the truth of that test.
In the second example, when you call an undefined method on nil, you get NoMethodError: undefined method 'foo' for nil:NilClass. Thus, the exception class is NoMethodError, and the message is undefined method 'foo' for nil:NilClass. Test the failing behavior in a console, and see what you get from it, and then apply that knowledge to the test.
Are you running
ruby path_to_enlightenment.rb
at the command prompt after you correct each test? It will give you lots of help.
Also "remember that silence is sometimes the best answer" -- if you are stumped don't put in anything and the tool will help you.
Well, in holding with the typical TDD motto of Red-Green-Refactor, you should run the test (probably with rake in a separate console) and see the failure happen. From there, they have provided you a few pieces of information about what was expected.
As for style, the koans aren't really teaching that. You should just find and read some code written in ruby to get a feel for the typical conventions and idioms of the ruby community.
Simplicity is the key with Ruby Koans - when I started it I thought it must be harder than what it is, but it's not! Just ask IRB the question Koans is asking you, and after a few you get a feel for it. I've written a blog piece about it to help others, too:
Ruby Koans Answers
I remember when I did this that I tried to out think the test and tried to put in
<Answer> and <"Answer">
The thing to remember is that the actual class doesn't have to be in a string or something.
So the answer is NOT
ex.class, ex.class
As suggested above, put the code into irb and execute it.
(1..5).class == Range
is a big hint

Resources