RSpec: How to Stub Only One of Multiple Calls to Same Method - ruby

I'm having trouble figuring out how to stub only one of two calls to a method. Here's an example:
class Example
def self.foo
{ a: YAML.load_file('a.txt'), # don't stub - let it load
b: YAML.load_file('b.txt') } # stub this one
end
end
RSpec.describe Example do
describe '.foo' do
before do
allow(YAML).to receive(:load_file).with('b.txt').and_return('b_data')
end
it 'returns correct hash' do
expect(described_class.foo).to eq(a: 'a_data', b: 'b_data')
end
end
end
The test fails because I have stubbed a call to YAML.load_file with the args for the second call ('b.txt'), not the first one it encounters('a.txt'). I thought argument matching would address this but it does not.
Failures:
1) Example.foo returns correct hash
Failure/Error:
{ a: YAML.load_file('a.txt'),
b: YAML.load_file('b.txt') }
Psych received :load_file with unexpected arguments
expected: ("b.txt")
got: ("a.txt")
Please stub a default value first if message might be received with other args as well.
Is there a way I can allow the first call to YAML.load_file to go through but only stub the second call? How would I do this?

There is a and_call_original option (see rspec docs).
Applied to your example, this should do what you are looking for:
before do
allow(YAML).to receive(:load_file).and_call_original
allow(YAML).to receive(:load_file).with('b.txt').and_return('b_data')
end

Related

Rspec for checking of a number

I am a ruby newbie, I have managed to pull out the code for ruby but writing rspecs for them seems problematic. It's hard to understand the way to write rspecs even after reading few tutorials. Someone please help me to write for an input method then I would try to refactor it for the rest.
RB file:
module RubyOperations
class Operations
def input(num)
RubyOperations.log('Enter a number:[Between 1 to 10]',:BOTH)
num = Integer(gets.chomp)
raise StandardError if num <= 0 || num > 10
return num
rescue StandardError, ArgumentError => e
RubyOperations.log(e,:ERROR)
end
end
end
RSPEC:
describe 'RubyOperations' do
describe 'Operations' do
describe '.input' do
context 'when number is provided' do
it 'returns the number provided' do
expect(RubyOperations.input(num)).to eq(Integer)
end
end
end
end
end
You can check the class of the output of the method to equal integer
require 'ruby_final_operations'
describe 'RubyOperations' do
describe 'Operations' do
describe '.input' do
context 'when number is provided' do
it 'returns the number provided' do
expect(RubyOperations.input(num).class).to eq(Integer)
(or)
expect(RubyOperations.input(num)).to be_a_kind_of(Integer)
end
end
end
end
end
And whenever you write rspec keep in mind
If the method for which you are writing rspec deals with manipulations in your db then check if db is manipulated or not
Or if you are writing rspec for any methods which returns an object then procced like this
if a method is defined like
def square_of_a_number(num)
num*num
end
Then write rspec like this
it 'returns square of a number' do
expect(square_of_a_number(2).to eq(4)
end
For any methods that you know the output of a method will be that then hardcode the input or user Faker gem for input of the method expect the expected result of that method
There are few issues with code that you have shared:
1) In the Operations class, the method input receives an argument which is not used anywhere because of this line: num = Integer(gets.chomp). Basically gets is the method that waits for user input, and the assignment num = ... overrides the value of argument (num) that is passed into the method, hence it is pointless to pass num argument into the method.
2) In your spec sample you call input method on RubyOperations module, while the input lives in class Operations under namespace RubyOperations. Also method input is not a class method but instance method. So proper method call would be: RubyOperations::Operations.new.input(5)
3) To run a spec for input method you would need to stub user input. RSpec-mocks gem can help you with that - https://github.com/rspec/rspec-mocks. It has allow stub method: allow(object).to receive(:gets) { "5" }
The whole sample will be:
it 'returns the number provided' do
# instantiate object that we will test
subject = RubyOperations::Operations.new
# we stub method 'gets' and whenever it is called we return string "5"
allow(subject).to receive(:gets) { "5" }
# we call method input with argument 1, the argument 1 is pointless as described in point 1) and can be omitted
expect(subject.input(1)).to eq(5)
end

Stub a function which is inside the function to be tested

I have a function let's say A whose output and functionality I have to test, A calls another function B which takes a lot of time to compute the output. So I am trying to use stubs to mimic all the values that B returns.
def A
#do something
output = B
#do something with output
end
Now the test files
describe "check what A returns" do
ClassName.stub(:B) do
[0, 1]
end
test_values = TestClass.A(input parameters)
#checks on test values
end
My aim is to pass the expected output of B to function A. I am using RSpec. How do I go about it?
With RSpec you can do:
allow(ClassName).to receive(:B).and_return([1,2,3,4,5])
After this you can call B function and it will return [1,2,3,4,5]
You can find more info at RSpec documentation: https://relishapp.com/rspec/rspec-mocks/v/3-4/docs/configuring-responses/returning-a-value
I've attempted to write some classes and test cases for what it seems like you want to test. The key here is to use allow to stub out the return value for a method.
Just note here that I've changed the methods in your class to be class methods to fit what seems to be your test case, but you can obviously change them back to instance methods to fit your purpose. Also, accepted Ruby style is to have lowercase method names.
class ClassName
def self.B
# returns something that we're going to stub out
end
end
class TestClass
def self.A
# do something
output = ClassName.B
# do something with output
# eg in this case, add a value to it
output << 2
end
end
describe TestClass do
describe '.A' do
before do
allow(ClassName).to receive(:B).and_return([0, 1])
end
it 'does something with output' do
expect(described_class.A).to eq([0, 1, 2])
end
end
end
There's ways as mentioned in other posts but I'll give you another: you might want to make that dependency explicit.
Here's how it could look like:
# test_class.rb
class TestClass
# The default will be automatically setup to be an object of type ClassName
def initialize(some_collaborator: ClassName.new)
self.some_collaborator = some_collaborator # Some people will probably also insert some guard (to make sure it responds to `b`
end
def a
# your code calling `some_collaborator.b`
end
private
attr_accessor :some_collaborator
end
# test_class_spec.rb
describe TestClass do
let(:stub_b) { stub("Some instance of ClassName", b: [...] }
subject { TestClass.new(some_collaborator: stub_b) }
it "whatever" do
expect(subject.a).to ...
end
end
The default collaborator should be a sensible default (and if you can't instantiate it there's ways to encapsulate it anyways). Not only it will be easier to read, but it will be easier to maintain.

RSpec stubbing and checking arguments when an object's constructor instantiates another

Given the code below, how would I go about verifying in an RSpec test
that the constructor of class B calls the constructor of class A with
the right argument?
class A
def initialize(*args)
end
end
class B < A
def initialize
super(1)
end
end
The test I have works as long as I don't check the arguments (leave out the ".with")
describe B do
describe '#new' do
it {
allow(A).to receive(:new).with(any_args)
B.new
expect(A).to have_received(:new).with(1)
}
end
end
Adding the check for the arguments gives me the following error:
B
#new
should have received new(1) 1 time (FAILED - 1)
Failures:
1) B#new should have received new(1) 1 time
Failure/Error: expect(A).to have_received(:new).with(1)
#<A (class)> received :new with unexpected arguments
expected: (1)
got: (no args)
This seems to indicate the constructor for class A gets called without
arguments?
When you call super in your initialize method for B, it calls initialize in A, not new. You could test that an instance of A receives initialize with one argument:
expect_any_instance_of(A).to receive(:initialize).with(1)
B.new
But RSpec will complain at you for stubbing initialize.
You're maybe better off checking for side effects (why do you want super to be called?) or verify that super was called:
expect_any_instance_of(B).to have_received(:super).with(1)

Why is this Ruby conditional assignment not memoized?

Can someone explain why this spec is failing?
RSpec results:
User#presenter memoizes presenter
Failure/Error: user.presenter
(<UserPresenter (class)>).new(...)
expected: 1 time with any arguments
received: 2 times with arguments: (...)
From my understanding of the conditional assignment operator it should only assign #presenter once.
# app/models/user.rb
def User
def presenter
#presenter ||= UserPresenter.new(self)
end
end
# app/presenters/user_presenter.rb
class UserPresenter
end
# spec/models/user_spec.rb
describe User do
let(:user){ build_stubbed(:user) }
describe "#presenter" do
it "memoizes presenter" do
UserPresenter.should_receive(:new).once
user.presenter
user.presenter # should not call .new
end
end
end
The issue is this line:
UserPresenter.should_receive(:new).once
This chunk of code simply sets up the expectation that a UserPresenter will receive the new method one time. It tears down your original UserPresenter#new method and replaces it with a method that returns nil. Because nil is falsy, the #presenter instance variable is not memoized.
To fix this, you can specify a return value in your expectation:
UserPresenter.should_receive(:new).once.and_return "some truthy value"
or equivalently
UserPresenter.should_receive(:new).once { "some truthy value" }
or if you absolutely want to call the original method
UserPresenter.should_receive(:new).once.and_call_original
or with the new expect syntax
expect(UserPresenter).to receive(:new).once.and_call_original
See this for more information about expecting a message, and this for more information about calling the original method. This has some further discussion about RSpec's should_receive method.

Ruby: Passing a block to a class macro that defines instance methods

I'm struggling with code that looks like the example below (but actually does something useful). The block that is passed to def_my_method is of course created in the context of the class, when I want to evaluate it in the context of the instance that has the instance method. How do I do this?
module Formatters
# Really useful methods, included in several classes
def format_with_stars(str)
return "*** #{str} ***"
end
end
class Test
include Formatters
STRINGS = ["aa", "bb"]
def self.def_my_method(method_name, another_parameter, &format_proc)
define_method(method_name) do
# In reality, some more complex code here, then...
return STRINGS.map(&format_proc)
end
end
def_my_method(:star_strings, "another_parameter") { |str| format_with_stars(str) }
# Define other methods
end
tt = Test.new
puts tt.star_strings
# Throws undefined method `format_with_stars' for Test:Class (NoMethodError)
You can use instance_exec to execute the passed block in the right context. Instead of passing &format_proc directly to the call to map, pass a block that calls it using instance exec.
Something like this:
def self.def_my_method(method_name, another_parameter, &format_proc)
define_method(method_name) do
# In reality, some more complex code here, then...
return STRINGS.map{|str| instance_exec(str, &format_proc)}
end
end
This produces this:
$ ruby tt.rb
*** aa ***
*** bb ***
for me (where tt.rb is the arbitary name I gave the file), which I think is what you want for this example.
...
class Test
- include Formatters
+ extend Formatters
...
should do the trick.

Resources