RSpec - Testing methods that call private methods that should be mocked - ruby-on-rails

I'm using RSpec for testing my classes on Rails.
I'm wondering what is a good way to test methods calling private methods.
For example I have this class:
Class Config
def configuration(overrides)
#config.merge(overrides)
end
private
def read_config_from_yml
#config ||= YAML.load()...
end
end
To test the configuration method, we need to somehow mock the read_config_from_yml method. I know it's not good to simply mock the private method read_config_from_yml or the instance variable #config because that would be messing with the internals of the object.
What I can think of on top of my head:
make read_config_from_yml public
add setter method for config (to avoid mocking the instance variable)
Are these hacks? Any other ideas?

One idea would be to actually create a copy of the YAML file in the test. You could take a snippet of the file that you're using in your production code, write it to the expected file location and delete it upon test completion.
before do
File.open(file_path_here, 'w+') do |f|
f << <<-eof
config:
setting1: 'string'
setting2: 0
eof
end
end
after do
File.delete(file_path_here)
end
it 'does the thing' do
...
end
This would avoid any stubbing and allow you to keep your method private.

Related

How can I run a private method in a module in rails console?

I have an error in a private method in a helper file. The helper file looks something like below with module HxHelper. I want to run method_2 in rails console to recreate the error in my local system.
module HxHelper
def method_1{
"key_1": "#h.htype"
"key_2": "value_2"
+ method_2}
end
private
def method_2{
"key1": "value_1"}
end
In my controller file I define a new class and include the previous helper file. I have tried the following.
Created an object of my class as obj = Class.new and obj.method_1. I get an error undefined method type in method_1. hype is attribute in house table.
Tried HxHeleper::method_1: Error - method_1 is not defined in HxHelper module.
Defining method_2 as self: Doesn't work.
Can someone help me understand what I am doing wrong?
Assuming we can get around your syntax issues, the standard answer to 'how do I call a private method in Ruby?' is with .send(), as in obj.send(:private_method).
After you manually debug this, learn to write automated tests. Rails has exemplary, industry-leading support for them. The tests will take over the role of experimentation and investigation that you are currently abusing the console for.
You have some syntax errors in this example. You want to use commas at the end of the lines of your hash, you can't add two hashes together, you instead need to merge. Merging will take the receiver (the thing you're calling merge on) and override any values from the argument. Additionally, when using : in your hash, your keys end up being symbols, which means you don't need the quotes.
This would be the proper way to define the helper module.
module HxHelper
def method_1
{
key_1: "#h.htype",
key_2: "value_2",
}.merge(method_2)
end
private
def method_2
{
key1: "value_1",
}
end
end
Then you can use it like this:
class Test
include HxHelper
end
t = Test.new
t.method_1
This will return:
{:key_1=>"#h.htype", :key_2=>"value_2", :key1=>"value_1"}
If you call t.method_2, you get an error about calling a private method.
If method_2's hash had a key of key_1 instead, your return value would be:
{:key_1=>"value_1", :key_2=>"value_2"}
because the :key_1 from the argument overrode the one on the receiver Hash.
If you wanted to call that private method, you could do:
t.send(:method_2)
If you had a method that took arguments, you just add them after the symbol of the method name:
private
def test(num1, num2)
return(num1 + num2)
end
send(:test, 1, 2)
This would return 3

Ruby/Rails testing - access variable outside of scope?

I want to unit test a method with rspec for RoR and have a method like this:
def create_record(obj, params)
begin
obj.add_attributes(params)
result = obj.save
rescue
MyMailer.failed_upload(#other_var, obj.api_class_name, params).deliver_now
end
end
create_record is never invoked directly, but through another method which fills in #other_var appropriately.
How should I go about testing the code to make sure MyMailer is called correctly? Should I have passed #other_var into the method instead of relying on it being filled in elsewhere (aka: is this a code smell?)? Thanks!
In Ruby you can use Object#instance_variable_set to set any instance variable.
RSpec.describe Thing do
describe "#create_record" do
let(:thing) do
t = Thing.new
t.instance_variable_set(:#other_var, "foo")
t
end
# ...
end
end
This completely circumvents any encapsulation which means that the use of instance_variable_set can be considered a code smell.
Another alternative is to use RSpecs mocking and stubbing facilities but stubbing the actual object under test is also a code smell.
You can avoid this by passing the dependency as a parameter or by constructor injection:
class Thing
attr_accessor :other_var
def initialize(other_var: nil)
#other_var = other_var
end
def create_record(obj, attributes)
# ...
end
end
A good pattern for this is service objects.

How to write RSpec for a controller method not accessible via GET and uses params?

def method1
if params[:hotel].present?
end
end
def method2 //accessed via GET
#hotel = params[:hotel]
method1
end
So, now I want to write a RSpec test for method1. How do I preset the params variable? Just to be clear, I cannot write
get :'method1', request_params
You can do this by literally setting the params yourself and calling the method on the controller eg:
expect(#controller).to receive(:params).and_return(:hotel => "Sample Hotel")
expect(...) # your spec expectations here
expect(#controller.send(:method1)).to eq(expected_return_value)
As mentioned, though, it is considered not best practice to test private/non-action methods of your controller...
Instead you're expected to test the public actions, and test that your private methods are doing the right thing by passing in all the possible variations and getting the final results - eg instead of specifically testing method1, you'd instead test method2 with all the variants that method1 should expect to respond to correctly.

Rspec private method can't access class instance variable?

I am testing my class' initialize method. It calls a private method, and for some reason that method is failing.
Class (code simplified for brevity):
class MyClass
#configs = {}
def initialize(configs)
#configs = configs
check_configs
create_client
end
private
def check_configs
if #configs['some_token'].nil?
Rails.logger.git_loader.error('log message')
raise AnError
end
end
end
The test:
describe '#initialize' do
let(:config) { my_token: '123-FakeToken' }
let(:loader) { described_class.new(config) }
context 'when initialized with a set of configs' do
it { expect(loader.instance_variable_get(:#configs)).to eq(configs)}
end
end
When I put a puts before the nil? check, the token prints out nothing, though when my rake task calls the initialize method, it prints fine.
Your example is a bit confusing due to the various spelling errors in your attempt to generalize your problem. I created the following two files, and the specs ran just fine. Might be a naming error that you're experiencing and not an actual rspec problem.
# test_spec.rb
require 'rspec'
require_relative 'my_class'
describe MyClass do
describe '#initialize' do
let(:configs) { {some_token: '123-FakeToken'} }
let(:loader) { described_class.new(configs) }
context 'when initialized with a set of configs' do
it { expect(loader.instance_variable_get(:#configs)).to eq(configs)}
end
end
end
and
# my_class.rb
class MyClass
##configs = {}
def initialize(configs)
#configs = configs
check_configs
end
private
def check_configs
if #configs[:some_token].nil?
puts "log message"
raise 'an error'
end
end
end
That said, the bigger question is what are you trying to accomplish with your tests?
Testing private variables is a smell. Ideally, in the case of config variables, they will cause an effect in your instance when set. For example MyClass.foo will behave differently based on whether some_token is set or not. Testing whether the behaviour of foo changes with a some_token present or not is a high value test that you want. Instead of a low value test of whether you wrote #configs = configs correctly.
Because you made your method check_configs in a private scope, you're unable to access it from the initialize method because the object you're initializing is looking for a method that is essentially hidden to the class. You either need to make the method public or rework your initialize method to not include private methods.
note, I came back and noticed this was not clear enough.
Initialize methods are always public, therefore they cannot include any private scoping within the method. You could call a private method anywhere else within the class except the initialize method
http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Classes#Private

Reopening a namespaced class in ruby -- initialize overridden?

I've got a class in a namespace with a few methods
module Foo
module Bar
class Baz
def initialize(arg1, arg2, arg3)
# do stuff
end
def delete
File.delete(#path)
end
end
end
end
In my test environment, I don't want delete to delete any files, so in a TestHelper, I do this
class Foo::Bar::Baz
def delete
puts "no delete in test"
end
end
When I initialize this class this in my test, I get ArgumentError: wrong number of arguments (3 for 0). That is, the initialize method of Baz is gone. And to be sure, if I take a look at self in my test helper, there are no methods defined at all for Baz. It's been completely overridden by the class keyword.
I can make it work by using class_eval instead of class, i,e.
Foo::Bar::Baz.class_eval do
def delete
# etc
end
end
My question is, what is the difference? Why does the latter work but not the former?
I could be wrong, but I think you're accidentally breaking the autoloader. Here's what I think is happening in your working case (using .class_eval):
Something, somewhere, loads code that defines Foo::Bar (you'd be getting other errors if this wasn't happening)
Test code is parsed; explicitly requires TestHelper
TestHelper references Foo::Bar::Baz, which does not exist
autoloader finds and loads foo/bar/baz.rb
TestHelper runs class_eval and redefines #delete
Test code runs
And here's my guess at the non-working case:
Again, something, somewhere, loads code that defines Foo::Bar
Test code is parsed; explicitly requires TestHelper
TestHelper creates Foo::Bar::Baz, since it didn't already exist
Test code runs
Notice in the second case the autoloader was never triggered, so your actual class definition is never loaded.
I'm not sure the best way to solve this. You could do an explicit require in your test, or just reference the class in your helper before redefining it:
Foo::Bar::Baz # trigger autoloading before we muck with the definition
class Foo::Bar::Baz
def delete
puts "no delete in test"
end
end

Resources