Rspec how to mock function call through instance method - ruby

I am trying to test a method which uses instance variable to call featureEnabled method. I am trying to write a rspec unit test for this. I am using below setup in the allow statement. Not sure how else to do this
exception: => #<NoMethodError: undefined method `feature_enabled?' for nil:NilClass>
Api.controllers :Customers do
#domain = current_account.domain
def main
t1 = #domain.featureEnabled?("showPages")
blah
Test:
RSpec.describe ApiHelpers do
describe "#find_matching_lists" do
let(:domain) { Domain.new }
it "madarchod3" do
allow(domain).to receive(:featureEnabled?).with("showPages").and_return(true)
end
end

Variables defined in a block are local to this block, they do not exist outside of it.
The problem you're having is that the domain variable you create with let is only visible inside the block passed to describe. Your test must be defined in that describe block for it to access this variable, ie this should work:
describe "#main" do
let(:domain) { Domain.new }
it "check if feature enabled" do
allow(domain).to receive(:featureEnabled?).with("showPages").and_return(true)
end
end

Related

Undefined local variable or method `translator' for main:Object (NameError)

I've used a gem and tried to create a method (trans) in my code.
require 'yandex-translator'
translator = Yandex::Translator.new(api_key)
def trans(text)
a = translator.translate text, to: "ru"
return a
end
puts trans("stack")
When I run the code, I get this error:
'trans': undefined local variable or method `translator' for main:Object (NameError)
Why did I get this error, and how can I solve this?
translator variable in this code is defined on class level, hence it’s a local variable in main context (since the whole code is executed in main context.)
You are trying to call it from the instance context, where it is obviously not defined. The easiest way to overcome it, would be to define #translator as being a class’ instance variable:
#translator = Yandex::Translator.new(api_key)
def trans(text)
#translator.translate text, to: "ru"
end
Because in this way you are looking for a local variable translator and you have not. Some solutions:
make translator global
$translator = Yandex::Translator.new(api_key)
or pass translator to trans method
def trans(translator, text)
translator.translate text, to: "ru"
end

how do I use rspec to test instance methods in ruby?

I have an instance method that would be invoked after creating a new instance of the class.
How do I test it in Rspec? I use the following and get an error:
let(:schedule) { ScheduleKaya.new('test-client-id') }
let(:schedule) { schedule.create_recurring_event('test-keyword', 'slack') }
In other words, I want to create the instance. Then I want to apply a method create_recurring_event.
My test wants to check if it assigned the variables to the instance.
it "has #keyword = test-keyword" do
expect(schedule.keyword).to eq('test-keyword')
end
Because it makes a database call, I want to check the response back from the call to see if I get a status = 200.
But I can't seem to both create the instance and then apply the method.
Question:
What is the right way to test for an instance method, one that is applied after creating a new instance.
A let block acts like a method and returns the return value of the last statement. Therefore just write both into the same block and ensure that the right value is returned:
let(:schedule) do
schedule_kaya = ScheduleKaya.new('test-client-id')
schedule_kaya.create_recurring_event('test-keyword', 'slack')
schedule_kaya
end
Or you can use tap:
let(:schedule) do
ScheduleKaya.new('test-client-id').tap do |schedule_kaya|
schedule_kaya.create_recurring_event('test-keyword', 'slack')
end
end
I suggest FactoryGirl together with Rspec, if you are on Railsfactory_girl_rails, looks like below:
it "has #keyword = test-keyword" do
schedule = Factory(:shcedule, keyword: "has #keyword = test-keyword")
expect(schedule.keyword).to eq('test-keyword')
end

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

How to return the receiver instance's self from should_receive block

I'd like to have instance methods of a class return self, and be init with another class instance self.
However I'm struggling to see how to spec this succintly:
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
end
When running this spec, this ensures that the next method is called on true instead of the Api instance, as expected, that is the return value of the block. Example:
class Cli
def eg
api = Api.new(self)
api.blowup # undefined method for true
end
end
I'd really like the block to return the Api instance self without invoking another call to Api.new(...) in the spec, the example below does this and to my mind a non-rspec reader would wonder why the spec passes when clearly Api.new(...) has been called more than once.
Can anyone suggest how best to do this?
Current solution:
This reads like ::Api.new(...) is called thrice: once to create api, once to create cli, once to create start. Yet the spec of one call passes. I understand why and that this is correct, so not a bug. However I'd like a spec that a reader not familiar with rspec could scan and not have the impression that Api.new has been called more than once. Also note that ...once.and_return(api){...} does not work, the block needs to return api in order to pass.
let(:cli){ ::Cli.start(['install']) }
let(:start){ ::Cli.start(['install']) }
it 'is the API' do
api = ::Api.new(cli)
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
api
end
start
end
You can save the original method (new) in a local variable and then use it to return the new api from within the block:
original_method = ::Api.method(:new)
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
original_method.call(arg)
end
This will run the expectation, checking that the argument is an instance of ::Cli, and then return the value from the original method (i.e. the api).

Shoulda: How would I use an instance variable outside of a setup or should block?

I'm trying to do something like the following:
#special_attributes = Model.new.methods.select # a special subset
#special_attributes.each do |attribute|
context "A model with #{attribute}" do
setup do
#model = Model.new
end
should "respond to it by name" do
assert_respond_to #model, attribute
end
end
end
However, #special_attributes is out of scope when running the unit tests, leaving me with a nil object on line 2. I can't figure out where/how to define it to bring it in scope. Any thoughts?
Got it (I think). Shoulda is executing the block in the context of Shoulda::Context. In the above case, #special_attributes is an instance variable of my test class, not Shoulda::Context. To fix this, instead of using instance variables, just use local variables in the context block.
So, for example:
context "Model's" do
model = Model.new
special_attributes = model.methods.select # a special subset
special_attributes.each do |attribute|
context "attribute #{attribute}" do
setup do
#model = model
end
should "should have a special characteristic"
assert_respond_to #model, attribute
...
end
end
end
end

Resources