rspec expectations on block - ruby

I have the below code under test:
class MethodCache
##methods=Hash.new
def self.add_method(name, &block)
##methods[name]=block
end
def self.get_method(name)
##methods[name]
end
end
Now my spec looks like this:
describe MethodCache do
subject {MethodCache}
foo_block = ->{ puts "foo"}
it ".get_method" do
subject.add_method "foo", &foo_block
# does not work
# expect(subject.get_method("foo").to be &foo_block
# should syntax works
subject.get_method("foo").should be foo_block
end
end
I am trying to stay away from should syntax and use the expect syntax of RSpec. However it does not work in this case.
expect(subject.get_method("foo").to be &foo_block fails saying wrong number of arguments. I guess this is because the expectation block is treated as a block argument.
expect(subject.get_method("foo").to be foo_block (without the '&') does not work either. It says, the matcher expects a value and not argument.
What am I missing here?

Related

Using RSpec to test user input with gets

I'm new to Unit Testing using RSpec and Ruby and I have a question on how to test if my code is using the gets method, but without prompting for user input.
Here is the code I'm trying to test. Nothing crazy here, just a simple one liner.
my_file.rb
My_name = gets
Here's my spec.
require 'stringio'
def capture_name
$stdin.gets.chomp
end
describe 'capture_name' do
before do
$stdin = StringIO.new("John Doe\n")
end
after do
$stdin = STDIN
end
it "should be 'John Doe'" do
expect(capture_name).to be == 'John Doe'
require_relative 'my_file.rb'
end
end
Now this spec works, but when I run the code it prompts for user input. I don't want it to do that. I want to simply test if the gets method is being called and possibly mock the user input. Not to sure how to achieve this in RSpec. In Python I would utilize unittest.mock is there a similar method in RSpec?
Thanks in advance!
Here's how you could stub gets with your return value.
require 'rspec'
RSpec.describe do
describe 'capture_name' do
it 'returns foo as input' do
allow($stdin).to receive(:gets).and_return('foo')
name = $stdin.gets
expect(name).to eq('food')
end
end
end
Failures:
1) should eq "food"
Failure/Error: expect(name).to eq('food')
expected: "food"
got: "foo"
(compared using ==)
To test if something is being called (such as a function) you can use expect($stdin).to receive(:gets).with('foo') to ensure it is being called (once) with the right args. The expectation line in this scenario has to go before name = $stdin.gets.

Rspec 3.0 How to mock a method replacing the parameter but with no return value?

I've searched a lot and just cannot figure this out although it seems basic. Here's a way simplified example of what I want to do.
Create a simple method that does something but doesn't return anything, such as:
class Test
def test_method(param)
puts param
end
test_method("hello")
end
But in my rspec test I need to pass a different parameter, such as "goodbye" instead of "hello." I know this has to do with stubs and mocks, and I've looking over the documentation but can't figure it out: https://relishapp.com/rspec/rspec-mocks/v/3-0/docs/method-stubs
If I do:
#test = Test.new
allow(#test).to_receive(:test_method).with("goodbye")
it tells me to stub out a default value but I can't figure out how to do it correctly.
Error message:
received :test_method with unexpected arguments
expected: ("hello")
got: ("goodbye")
Please stub a default value first if message might be received with other args as well.
I am using rspec 3.0, and calling something like
#test.stub(:test_method)
is not allowed.
How to set a default value that is explained at
and_call_original can configure a default response that can be overriden for specific args
require 'calculator'
RSpec.describe "and_call_original" do
it "can be overriden for specific arguments using #with" do
allow(Calculator).to receive(:add).and_call_original
allow(Calculator).to receive(:add).with(2, 3).and_return(-5)
expect(Calculator.add(2, 2)).to eq(4)
expect(Calculator.add(2, 3)).to eq(-5)
end
end
Source where I came to know about that can be found at https://makandracards.com/makandra/30543-rspec-only-stub-a-method-when-a-particular-argument-is-passed
For your example, since you don't need to test the actual result of test_method, only that puts gets called in it passing in param, I would just test by setting up the expectation and running the method:
class Test
def test_method(param)
puts param
end
end
describe Test do
let(:test) { Test.new }
it 'says hello via expectation' do
expect(test).to receive(:puts).with('hello')
test.test_method('hello')
end
it 'says goodbye via expectation' do
expect(test).to receive(:puts).with('goodbye')
test.test_method('goodbye')
end
end
What it seems you're attempting to do is set up a test spy on the method, but then I think you're setting up the method stub one level too high (on test_method itself instead of the call to puts inside test_method). If you put the stub on the call to puts, your tests should pass:
describe Test do
let(:test) { Test.new }
it 'says hello using a test spy' do
allow(test).to receive(:puts).with('hello')
test.test_method('hello')
expect(test).to have_received(:puts).with('hello')
end
it 'says goodbye using a test spy' do
allow(test).to receive(:puts).with('goodbye')
test.test_method('goodbye')
expect(test).to have_received(:puts).with('goodbye')
end
end

Regex error inside class-eval block

I've got some code to add methods to a module from simple definitions for talking to remote resources via a wrapper class around a REST client.
def service_function(function_name, method, uri, parameters)
class_eval <<-RUBY
def #{function_name}(params)
if !(#{function_name}_required_params - params.keys).empty? || \
!(params.keys - #{function_name}_params).empty?
raise Errors::InvalidParameters.new(service_name, __method__,
params.keys, #{function_name}_params)
end
WebServices::ServiceRequest.perform(self.domain, #{uri}, #{method}, params)
end
def #{function_name}_params
#{function_name}_required_params + #{function_name}_optional_params
end
def #{function_name}_required_params
#{parameters}.select { |param,req| req }.keys
end
def #{function_name}_optional_params
#{parameters}.select { |param,req| !req }.keys
end
RUBY
end
Before I can even run the code, just requiring the gem I'm building into IRB spits out this error:
1.9.2p320 :001 > require 'web-services'
SyntaxError: (eval):7: unknown regexp options - rt
The offending line is:
WebServices::ServiceRequest.perform(self.domain, #{uri}, #{method}, params)
Removing the "#{uri}" argument fixes it, even leaving in the "#{method}" argument. Does anyone out there have a clue as to why this might be? I'm about at my wit's end.
You have a url that looks something like /something/rt and that will look like a regex literal in here:
WebServices::ServiceRequest.perform(self.domain, #{uri}, #{method}, params)
You need to escape and quote #{uri} so that it looks like a string inside the heredoc, that way class_eval will see perform(..., '/something/rt', ...) and be happy. You might have similar issues with #{method}.

Does should_receive do something I don't expect?

Consider the following two trivial models:
class Iq
def score
#Some Irrelevant Code
end
end
class Person
def iq_score
Iq.new(self).score #error here
end
end
And the following Rspec test:
describe "#iq_score" do
let(:person) { Person.new }
it "creates an instance of Iq with the person" do
Iq.should_receive(:new).with(person)
Iq.any_instance.stub(:score).and_return(100.0)
person.iq_score
end
end
When I run this test (or, rather, an analogous one), it appears the stub has not worked:
Failure/Error: person.iq_score
NoMethodError:
undefined method `iq_score' for nil:NilClass
The failure, as you might guess, is on the line marked "error here" above. When the should_receive line is commented out, this error disappears. What's going on?
Since RSpec has extended stubber functionality, now following way is correct:
Iq.should_receive(:new).with(person).and_call_original
It will (1) check expectation (2) return control to original function, not just return nil.
You're stubbing away the initializer:
Iq.should_receive(:new).with(person)
returns nil, so Iq.new is nil. To fix, just do this:
Iq.should_receive(:new).with(person).and_return(mock('iq', :iq_score => 34))
person.iq_score.should == 34 // assert it is really the mock you get

RSPEC bug with a class Parser (?)

I think I hit a bug in RSPEC bug, while just trying it for the first time...
In the following example, RSPEC is sensitive to the name of the class : with 'Parser' in parser.rb file the test fails, but just renaming it 'FooParser' makes it work.
require_relative './parser.rb'
describe Parser do
it 'should do the trick' do
#parser = Parser.new "test.pas"
end
end
will complain about my constructor argument, even having a plain code like this :
class Parser
def initialize arg
end
end
The RSPEC erroneous log looks like this :
1) Parser should do the trick
Failure/Error: #parser = Parser.new "test.pas"
ArgumentError:
wrong number of arguments(1 for 0)
The spec as written in your question should work (and accomplish nothing, by the way), but I'm guessing that in your real spec you tried to use should with an implicit subject, like this:
it 'should do the trick' do
#parser = Parser.new "test.pas"
should_not be_nil
end
If you use should or should_not bare like this, Rspec has to figure out what you're testing. It'll see if you are describing a class -- which you are -- and will try to instantiate it -- in your case using Parser.new with no arguments.
You may have wanted something like this:
it 'should do the trick' do
Parser.new("test.pas").should_not be_nil
end
This is a dumb test, but maybe it illustrates (what could be) the problem.

Resources