Stub a setter on RSpec instance_double - ruby

In an RSpec unit test I have a mock defined like this:
let(:point) { instance_double("Point", :to_coords => [3,2]) }
In the Point class I also have a setter, which is used in the class under test (which is called Robot). I would like to stub that setter to test Robot#move. Here's the wrong code I have so far:
describe "#move" do
it "sets #x and #y one step forward in the direction the robot is facing" do
point.stub(:coords=).and_return([4,2])
robot.move
expect(robot.position).to eq([4,2])
end
end
Here's the error message I get:
Double "Point (instance)" received unexpected message :stub with (:coords=)

Got it! The correct syntax looks like this:
allow(point).to receive(:coords=).and_return([4,2])
The stub method is apparently deprecated.

Another option is to stub the setter method in the definition of the double like so:
let(:point) { double("point", 'coords=' => [4,2]) }
See this github issue for details.

Related

How do I use rspec mock methods from inside a custom DSL

I'm writing a testing library that works on top of rspec. I have a custom dsl that looks like this:
rast Worker do
prepare do |day_type, dow|
allow(subject).to receive(:holiday?) { day_type == 'Holiday' }
allow(subject).to receive(:dow) { dow }
end
execute do
result subject.goto_work?
end
end
The two allow statements do not work because they are inside my custom DSL rast with the method prepare. How can I make it work?
Inside the execute method I invoke this prepare block like this:
def execute
prepare_block = #prepare_block
RSpec.describe "test" do
prepare_block&.call(*params)
...
I don't have the whole picture, but at a guess and off the top of my mind, you may fare better with something like
RSpec.describe "test" do
instance_eval(prepare_block, *params) if prepare_block
end
instance_eval will evaluate the block in the context of the receiver (so whatever self is inside the describe block).
If you just do prepare_block.call, it won't have access to any methods defined in the context where it happened to be called from, as you found out.
Good luck!

How to stub a method that is called in initialize method

I want to stub a method that is called in initialize method.
There is a class Company like this:
class Company
def initialize(code: code, driver: driver)
#driver = driver
#code = code
navigate_to_search_result
end
def navigate_to_search_result
# do something
end
end
And I want to stub the method navigate_to_search_result.
before(:each) do
company = Company.new(code: 7220, driver: Selenium::WebDriver.for(:phantomjs))
allow(company).to receive(:navigate_to_search_result){ true }
end
But this code fails because navigate_to_search_result is already executed by initializing.
How can I stub method like this?
One of the following lines should be present/run in your test before you instantiate a Company object i.e. before you do Company.new.
allow_any_instance_of(Company).to receive(:navigate_to_search_result){ true }
or
allow_any_instance_of(Company).to receive(:navigate_to_search_result).and_return(true)
Move the navigate_to_search_result method out of the initialize method and call it manually. The initialize method is typically only used for setup.
Company.stub(:new).and_return(Object)
Use doubles, like:
before
company = double(Company, code: 7220, driver: Selenium::WebDriver.for(:phantomjs))
allow(company).to receive(:navigate_to_search_result){ true }
end
With doubles, you can fake an instance of Company without initialize it. So it will not try run navigate_to_search_result before you stub it.
But what are you testing? Maybe stub might not be the better choice, and you might test the method navigate_to_search_result

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

rails rspec - how to check for a model constant?

How can I do something like:
it { should have_constant(:FIXED_LIST) }
In my model (active record) I have FIXED_LIST = 'A String'
It's not a db attribute or a method and I haven't been able to use responds_to or has_attribute to test for it (they fail). What can I use the to check for it. - btw I have the shoulda-matchers installed.
Based on David Chelimsky's answer I've got this to work by slightly modifying his code.
In a file spec/support/utilities.rb (or some other in spec/support) you can put:
RSpec::Matchers.define :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
Note the use of "RSpec::Matchers.define" in stead of "matchers"
This allows to test for constants in your specs, like:
it "should have a fixed list constant" do
YourModel.should have_constant(:FIXED_LIST)
end
Note the use of "have_constant" in stead of "have_const"
It reads a little silly, but:
describe MyClass do
it { should be_const_defined(:VERSION) }
end
The reason is that Rspec has "magic" matchers for methods starting with be_ and have_. For example, it { should have_green_pants } would assert that the has_green_pants? method on the subject returns true.
In the same fashion, an example such as it { should be_happy } would assert that the happy? method on the subject returns true.
So, the example it { should be_const_defined(:VERSION) } asserts that const_defined?(:VERSION) returns true.
If you want to say have_constant you can define a custom matcher for it:
matcher :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
MyClass.should have_const(:CONST)
If you're trying to use the one-liner syntax, you'll need to make sure the subject is a class (not an instance) or check for it in the matcher:
matcher :have_constant do |const|
match do |owner|
(owner.is_a?(Class) ? owner : owner.class).const_defined?(const)
end
end
See http://rubydoc.info/gems/rspec-expectations/RSpec/Matchers for more info on custom matchers.
HTH,
David
Another option to simply make sure the constant is defined – not worrying about what it's defined with:
it 'has a WHATEVER constant' do
expect(SomeClass::WHATEVER).not_to be_nil
end
A warning to anyone trying to test that constants are defined: If your code references an undefined constant while defining a class, then your specs will crash before they get to your test.
This can lead you to believe that
expect { FOO }.to_not raise_error
is failing to catch the NameError, because you'll get a big stack trace, instead of a nice "expected not to raise error, but raised NameError."
Amidst the huge stack trace, it can be difficult to notice that your test is actually crashing on line 1: requre "spec/spec_helper" because your entire application is failing to load before it gets to your actual test.
This can happen if you have dynamically defined constants, such as is done by ActiveHash::Enum, and you then use them in the definition of another constant. Don't bother testing that they exist, every spec in your app will crash if one of them fails to be defined.
You could use
defined? YOUR_MODEL::FIXED_LIST
In RSpec 2, I was able to get this to work in one line as follows:
it { subject.class.should be_const_defined(:MY_CONST) }
That is, check against the class, instead of the instance.
In My model
class Role < ActiveRecord::Base
ROLE_ADMIN = "Administrador"
end
In My rspec
RSpec.describe Role, type: :model do
let(:fake_class) { Class.new }
describe "set constants" do
before { stub_const("#{described_class}", fake_class) }
it { expect(described_class::ROLE_ADMIN).to eq("Administrador") }
end
end
For ruby 2.1.5 and rspec 3.5.0 I am able to test that constant SEARCH_CHARS_TO_IGNORE is defined in the class DiffAlertsDatatable as follows:
expect(DiffAlertsDatatable.const_defined?(:SEARCH_CHARS_TO_IGNORE)).to eq(true)

Rspec stubbing method for only specific arguments

Is there a way to stub method for only specific arguments. Something like this
boss.stub(:fire!).with(employee1).and_return(true)
If any other employee is passed to boss.fire! method, I'll get boss received unexpected message error, but what I would really like is just to override the method for specific argument, and leave it be for all others.
Any ideas how this can be done?
You can add a default stub for the fire! method which will call original implementation:
boss.stub(:fire!).and_call_original
boss.stub(:fire!).with(employee1).and_return(true)
Rspec 3 Syntax (#pk-nb)
allow(boss).to receive(:fire!).and_call_original
allow(boss).to receive(:fire!).with(employee1).and_return(true)
You can try write your own stubbing method, with code like this
fire_method = boss.method(:fire!)
boss.stub!(:fire!) do |employee|
if employee == employee1
true
else
fire_method.call(*args)
end
end

Resources