Rails / RSpec: How to test #initialize method?
For me, expectations are about designing conversations among collaborators. So, you have to decide - is #pick_seed
an internal implementation detail or part of a collaborating role's interface?
If pick_seed
is an implementation detail, an expectation is the wrong tool for the job. And, since you have an accessor for seed, you can proceed thusly (notice the one-assertion-per-example):
class Generator
attr_accessor :seed
def initialize(seed = nil)
@seed = seed || pick_seed
end
def pick_seed
Time.now.to_i
end
end
describe Generator do
context "creating" do
context "when a seed is specified" do
it "uses that seed" do
seed = 123
generator = Generator.new(seed)
generator.seed.should == seed
end
end
context "when a seed is not specified" do
it "creates its own seed" do
generator = Generator.new
generator.seed.should_not be_nil
end
end
end
end
OTOH, if picking the seed is part of the "seed picker" role, then mocks are valuable in designing the seed picker, and dependency injection is a standard method of assigning the roles. You could write something like:
class GeneratorWithCollaborator
attr_accessor :seed
def initialize(seed = nil, seed_picker = self)
@seed = seed || seed_picker.pick_seed
end
def pick_seed
Time.now.to_i
end
end
describe GeneratorWithCollaborator do
context "creating" do
context "when a seed is specified" do
it "uses that seed" do
seed = 123
seed_picker = double('seed picker')
seed_picker.should_not_receive(:pick_seed)
generator = GeneratorWithCollaborator.new(seed, seed_picker)
generator.pick_seed
end
end
context "when a seed is not specified" do
it "delegates to its seed picker" do
seed_picker = double('seed picker')
seed_picker.should_receive(:pick_seed)
generator = GeneratorWithCollaborator.new(nil, seed_picker)
end
end
end
end
RSpec: How to test init methods
You are searching for it:
expect_any_instance_of(Journey).to receive(:start_journey)
https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class
How to test initialize method of ruby which inturn calls the private method
I suggest rewriting it a bit with memorization.
module Plugin
module Rules
class RulesLoader
include Singleton
def fetch_rule_definition_for(key)
@definitions ||= load_definitions
@definitions[key]
end
private
def load_definitions
#Other code
hash = {}
#this method returns the hash after processing
end
end
end
end
A hypothetical spec:
describe Plugin::Rules::RulesLoader do
context '#fetch_rule_definition_for' do
subject(:fetch_rule_definition) { described_class.instance.fetch_rule_definition_for(key) }
context 'with valid key' do
let(:key) { :valid_key }
it 'returns definitions' do
expect(fetch_rule_definition).to eq(#YOUR_VALUE_HERE#)
end
end
context 'with invalid key' do
let(:key) { :invalid_key }
it 'returns nil' do
expect(fetch_rule_definition).to be_nil
end
end
end
end
How do I test with Rspec an initialize method that invokes find_or_initialize_by?
You could use
expect(document).to receive_message_chain(:template_variables, :find_or_initialize_by).exactly(8).times
but it's a mess and you would have to also check if each call got proper parameters.
I would suggest extracting this to a method: Document#initialize_variables(template_variables)
then you could test it as simply as
expect(document).to receive(:initialize_variables).with(expected_hash)
and then you can cover Document#initialize_variables
with specs and test it's behavior in depth.
RSpec - how to test if object sends messages to self in #initialize
Spies are an alternate type of test double that support this pattern
by allowing you to expect that a message has been received after the
fact, using have_received.
https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/basics/spies
Only spy object can store the method calls. To test your real class in the way that you want, you have to use expect_any_instance_of
statement before the class will be initialized:
expect_any_instance_of(Picture).to receive(:add_single_pics)
Picture.new('Test Picture', 'Test-Picture')
In this case your add_single_pics
method will be called, but its logic will not be run, if you need to run it you need to call the and_call_original
method on the matcher:
expect_any_instance_of(Picture).to receive(:add_single_pics).and_call_original
How to test initializer number of arguments in Ruby
The problem is that Card.new
is defined in Card.initialize
.
So:
it { expect(Card).to respond_to(:new).with(2).arguments }
is actually trying to test:
class Card
def self.new(number, suit)
...
end
end
You could simply try to initialize Card
with incorrect arguments and check that ArgumentError
is raised:
class Card
attr_reader :number, :suit
def initialize(number, suit)
@number = number
@suit = suit
end
end
describe Card do
context 'when initialized with incorrect number of arguments' do
[[], [1], [1, 2, 3], [1, 2, 3, 4]].each do |args|
it { expect { Card.new(*args) }.to raise_error(ArgumentError) }
end
end
context 'when initialized with correct arguments' do
it { expect(Card.new(1, 'D')).to be_a(Card) }
end
end
It outputs:
Card
when initialized with incorrect number of arguments
should raise ArgumentError
should raise ArgumentError
should raise ArgumentError
should raise ArgumentError
when initialized with correct arguments
should be a kind of Card
Finished in 0.01455 seconds (files took 0.64291 seconds to load)
5 examples, 0 failures
There might be a cleaner way, though.
RSpec and Object Initialization in Ruby
I. I was able to recreate your issue with a code like this
class Manager
def self.run(a, b)
new(a, b)
end
end
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
RSpec.describe Manager, type: :api do
let(:a) { 1 }
let(:b) { 2 }
describe '::run' do
it 'instantiates the class with 2 arguments' do
expect(Manager).to receive(:new).with(a, b)
Manager.run(a, b)
end
end
end
Which results in:
1) Manager#run instantiates the class with 2 arguments
Failure/Error: expect(Manager).to receive(:new).with(a, b)
Wrong number of arguments. Expected 0, got 2.
This happens because of the verifying functionality. When this setting is enabled (and it should be), RSpec will make sure the the object implements the interface that is being stubbed/mocked. In this case RSpec throws an error on the line expect(Manager).to receive(:new).with(a, b)
, because it actually looks into the Manager
class and checks whether initialize can take 2 arguments.
If you change the manager to look like this the example will pass:
class Manager
attr_reader :a, :b
def initialize(a, b)
@a = a
@b = b
end
def self.run(a, b)
new(a, b)
end
end
II. But you don't really need to use mocks for functionality like this. If you are just checking whether the right kind of instance is returned it is better to just look at the real thing.
RSpec.describe Manager, type: :api do
let(:a) { 1 }
let(:b) { 2 }
describe '::run' do
subject { described_class.run(a, b) }
it 'instantiates the class with 2 arguments' do
expect(subject).to be_an_instance_of(Manager)
end
it 'sets a to the first argument' do
expect(subject.a).to eq(a)
end
it 'sets b to the second argument' do
expect(subject.b).to eq(b)
end
end
end
III. In this example:
expect(Manager).to receive(:run).with(first_arg, second_arg)
Manager.run(first_arg, second_arg)
You set up an assertion, and then immediately called the code to pass that assertion. So weren't really testing anything.
Mocking/stubbing correctly is fairly advanced testing concept, and it is easy to get it wrong so if you can go without it, just go without, it will make things easier.
If you want to learn more about what to test/when to mock. I recommend this talk by Sandi Metz. https://www.youtube.com/watch?v=URSWYvyc42M
Related Topics
Ruby Way to Group Anagrams in String Array
Using Multiple Controllers in One View in Rails
Strong Parameters Require Multiple
How to Run Phantomjs on Heroku
Ruby on Rails, Paperclip, Amazon Aws S3 & Heroku
How to Generate PDF from Markdown Using Pure Ruby
Ruby Bundle Install Require: No Such Files to Load Error
How to Convert Character Encoding with Ruby 1.9
Get Link and Href Text from HTML Doc with Nokogiri & Ruby
Strategies for Overriding Database.Yml
Rails Implementation of Search with Autocomplete
How to Safely Join Relative Url Segments
Use Global or Constant Variable in Ruby/Rails
Pry Not Stopping When Called from a Ruby Script That Reads from Stdin
Why Can't I Install SASS on MAC Os Sierra
Rails 3.1 Actioncontroller::Routingerror (No Route Matches [Get] "/Assets/Rails.Png"):