how to access instant variable with rspec - ruby

I have a class like this.
require 'net/http'
class Foo
def initialize
#error_count = 0
end
def run
result = Net::HTTP.start("google.com")
#error_count = 0 if result
rescue
#error_count += 1
end
end
And I want to count up #error_count if connection fails, so I wrote like this.
require_relative 'foo'
describe Foo do
before(:each){#foo = Foo.new}
describe "#run" do
context "when connection fails" do
before(:each){ Net::HTTP.stub(:start).and_raise }
it "should count up #error_count" do
expect{ #foo.run }.to change{ #foo.error_count }.from(0).to(1)
end
end
end
end
Then I got this error.
NoMethodError:
undefined method `error_count' for #<Foo:0x007fc8e20dcbd8 #error_count=0
How can I access instance variable with Rspec?
Edit
describe Foo do
let(:foo){ Foo.new}
describe "#run" do
context "when connection fails" do
before(:each){ Net::HTTP.stub(:start).and_raise }
it "should count up #error_count" do
expect{ foo.run }.to change{foo.send(:error_count)}.from(0).to(1)
end
end
end
end

Try #foo.send(:error_count) i guess it should work.
Update: found in docs
expect{ foo.run }.to change{foo.instance_variable_get(:#error_count)}.from(0).to(1)

Related

How to write RSpec for method that raises error

Hi I want to write RSpec test for the method below
Class A
def test
val = nil
raise "invalid" unless var
end
end
Can someone please help me, how can I write RSpec for #test method when val is nil
I would do:
describe A do
let(:a) { A.new }
describe '#test' do
it "raises an error" do
expect { a.test }.to raise_error("invalid")
end
end
end

Monkey patching with mock in before block

Here's what ended up working:
# lib file
module SlackWrapper
class << self
def client
#client ||= ::Slack::Web::Client.new
end
end
end
describe SlackWrapper do
# test file
before :each do
$mock_client = double("slack client").tap do |mock|
allow(mock).to receive(:channels_info) { channel_info }
end
module SlackWrapper
class << self
def client
$mock_client
end
end
end
end
describe "#should_reply?" do
describe "while channel is paused" do
it "is falsey" do
SlackWrapper.pause_channel message.channel
expect(
SlackWrapper.should_reply? message
).to be_falsey
end
end
describe "while channel is not paused" do
it "is truthy" do
expect(
SlackWrapper.should_reply? message
).to be_truthy
end
end
end
end
This definitely does not feel right. However, leaving $mock_client as a local var gives me undefined local variable when tests are run, and moving the double... code into the monkeypatch gives undefined method. And of course, monkeypatching.
What's the correct way to do this?
You could just stub the new method for a test block or entire spec file:
# test file
# you could also create a class double if you need its methods:
# https://relishapp.com/rspec/rspec-mocks/v/3-9/docs/verifying-doubles/using-a-class-double
let(:slack_client) { double("slack client") }
before(:each) do
allow(::Slack::Web::Client).to receive(:new).and_return(slack_client)
end
# simple example:
it "checks slack client method to be a double" do
expect(SlackWrapper.client).to be(slack_client)
end

Preventing error from causing RSpec test failure

Suppose I have a class with methods like these:
class MyClass
...
def self.some_class_method
my_instance = MyClass.new
self.other_class_method(my_instance)
raise 'ERROR'
end
def self.other_class_method(instance)
...
end
end
And the test for it looks like this:
require 'spec_helper'
describe MyClass do
describe '.some_class_method' do
context 'testing some_class_method' do
it 'calls other_class_method' do
MyClass.should_receive(:other_class_method)
MyClass.some_class_method
end
end
end
end
The test errors out with ERROR, and if I remove the raise 'ERROR' line, the test passes. But here I want to only test whether some_class_method calls other_class_method, regardless of what happens afterwards. I could change it to expect the method to raise an error, but that's not the purpose of this particular test. Is there a better way?
You could rescue the exception in the test.
describe MyClass do
describe '.some_class_method' do
context 'testing some_class_method' do
it 'calls other_class_method' do
MyClass.should_receive(:other_class_method)
begin
MyClass.some_class_method
rescue
end
end
end
end
end
What about adding an expectation that the method is raising an error. That will even enhance your testing:
describe MyClass do
describe '.some_class_method' do
context 'testing some_class_method' do
it 'calls other_class_method' do
expect(MyClass).to receive(:other_class_method)
expect { MyClass.some_class_method }.to raise_error("ERROR")
end
end
end
end

What's best practice for mocking new method on different class

I have next scenario:
module Module
class CommandPattern
def initialize(value)
command = []
#var = value['something']
#abc = value['abc']
#command << value
end
def add(value)
#command << value
end
def get_command
#command
end
end
end
module Module
class Implementator
def initialize(value)
#value = value
end
def method_to_test(argument)
var = "command1"
cmd = CommandPattern.new(var)
var2 = "command2"
cmd.add(var2)
var3 = argument
cmd.add(var3)
commands = var + var2 + var3
commands
end
end
end
So, when I'm testing Module::B.method_I_want_to_test, what would be the best practice to mock "var = A.new(some_stuff)"? Beside refactoring and moving this line into separate method, is there some nice way to do this?
Little bit of background on this question - this style (Module::ClassA and Module::ClassB) - I'm using http://naildrivin5.com/gli/ and reason for this approach is that class A is actually implementing Command Pattern.
So issue I was apparently getting was due to wrong way of trying to write specs.
What I did before was (on the way how #spickermann advised):
RSpec.describe Module::Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
Issue was apparently in testing Module::Implementator, didn't realise I can put module around my RSpec.describe block and solve my first issue:
module Module
RSpec.describe Implementator do
describe "#method_to_test" do
let(:command_argument) { "command" }
let(:cmnd) { double(CommandPattern, :new => command_argument, :add => command_argument)}
subject(:method_to_test) do
Implementator.new("value").method_to_test("dejan")
end
before do
allow(CommandPattern).to receive(:new).with(any_args).and_return(cmnd)
allow(CommandPattern).to receive(:add).with(any_args).and_return(cmnd)
end
it 'does something' do
expect{ method_to_test }.not_to raise_error
end
it 'does something else' do
result = method_to_test
expect(result).to eq("command1command2dejan")
end
end
end
end
Another issue I had was global variable keeping YAML structure, which I missed to see and declare in spec_helper.rb
However, thank's to #spickermann's advices, issue is solved.
I would start with something like this:
describe '#method_I_want_to_test' do
let(:something) { # whatever something needs to be }
let(:a) { double(A, # methods you need from a) }
subject(:method_I_want_to_test) do
B.new(something).method_I_want_to_test
end
before do
allow(A).to receive(:new).with(something).and_return(a)
end
it 'does what I expect' do
expect(method_I_want_to_test).to eq(# what do_semething_else returns)
end
end
The interesting part is the before block that stubs the new method on A. It returns always the double defined in the let(:a) line instead of a real instance of A

Testing if some function call method on associated object in Rspec

Assume I have some method which call another method on some object:
def initialize
#obj = SomeClass.new
end
def method
#obj.another_method
end
How can I test this with Rspec and .should_receive?
You can do it by passing obj to your class. This technique is called Dependency Injection
http://sporto.github.io/blog/2013/09/25/simple-dependency-injection/
require "rspec"
class Foo
def initialize(obj = SomeClass.new)
#obj = obj
end
def method
#obj.another_method
end
end
describe Foo do
describe "#method" do
subject { Foo.new(obj) }
let(:obj){ mock }
it "delegates to another_method" do
obj.should_receive(:another_method).and_return("correct result")
subject.method.should eq "correct result"
end
end
end
You can also do it like this but it's very bad way of testing class internals
require "rspec"
class Foo
def initialize
#obj = SomeClass.new
end
def method
#obj.another_method
end
end
describe Foo do
describe "#method" do
it "delegates to another_method" do
subject.instance_variable_get(:#obj).should_receive(:another_method).and_return("correct result")
subject.method.should eq "correct result"
end
end
describe "#method" do
it "delegates to another_method" do
SomeClass.stub_chain(:new, :another_method).and_return("correct result")
subject.method.should eq "correct result"
end
end
describe "#method" do
let(:obj) { mock(another_method: "correct result") }
it "delegates to another_method" do
SomeClass.stub(:new).and_return(obj)
obj.should_receive(:another_method)
subject.method.should eq "correct result"
end
end
end
In my code I would use depedency injection and only test output which means no #should_receive at all
require "rspec"
class Foo
attr_reader :obj
def initialize(obj = Object.new)
#obj = obj
end
def method
obj.another_method
end
end
describe Foo do
describe "#method" do
subject { Foo.new(obj)}
let(:obj){ mock }
it "delegates to another_method" do
obj.stub(:another_method).and_return("correct result")
subject.method.should eq "correct result"
end
end
end
While the dependency injection provided by the other answer is preferable, given your existing code, you would need to do something like:
describe "your class's method" do
it "should invoke another method" do
some_mock = double('SomeClass')
SomeClass.should_receive(:new).and_return(some_mock)
someMock.should_receive(:another_method).and_return('dummy_value')
expect(YourClass.new.another_method).to eq('dummy_value')
end
end
where YourClass is the class in question.
Update: Added check for returned value with nod to #Lewy

Resources