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
Related
Here is the file I am trying to test
module Cesid
module Access
def cesid_signout
return unless session_logged
# Method to modify the cookie
set_cookie
end
def session_logged
session[:cesid_logged]
end
end
end
Here is my rspec file
describe Cesid::Access do
context '#cesid_signout' do
let!(:access) { Class.new { extend Cesid::Access } }
it 'does nothing if not cesid logged' do
session[:cesid_logged] = false
expect(access.session_logged).to eql(false)
end
end
end
I'm just getting an error after running the rspec. The logs just keep printing this and I can't view this first line of the error
# /Users/renee.sarmiento/.rvm/gems/ruby-2.5.1#ls-member/gems/actionpack-4.2.8/lib/action_dispatch/testing/test_process.rb:14:in `session'
Any ideas? Thank you!
I don't have all the context in your use case.
But it is clear that you don't have access to session into your cached variable access since session is not an instance variable.
So I suggest two things:
First, make sure that you can initiate your session instance variable into your module as explain here
module Cesid
module Access
def cesid_signout
return unless session_logged
# Method to modify the cookie
set_cookie
end
def session
#session ||= {}
end
def session_logged
#session[:cesid_logged]
end
end
end
Then, in your spec, make sure not only to instantiate your session but also access it the right way :
context '#cesid_signout' do
let!(:access) { Class.new { extend Cesid::Access } }
it 'does nothing if not cesid logged' do
access.session[:cesid_logged] = false
expect(access.session_logged).to eql(false)
end
end
With those modification, your RSpec tests should pass.
Of course it is one way to solve your problem, but depending on your use case, other solution might work.
Hope it'll help your understanding.
More insight of how module works here just in case.
This is my class
class MyClass
def run
to_be_mocked("arg")
## etc
end
private
def to_be_mocked(arg)
# implementation
end
end
and my Controller, which is what I am writing the request specs for, call this class.
This are my request specs:
context "Some context" do
context "some sub context" do
before :each do
allow(MyClass). to receive(: to_be_mocked).with(account.url).and_return(false)
end
it "responds with a 200" do
do_request
expect(JSON.parse(response.body)["field"]).to eq true
expect(response.status).to eq 200
end
end
However my mocking fails with an MyClass does not implement: to_be_mocked
Already tried removing the private keyword, but got the same results.
What am I missing here?
You're mocking on the class, which is how you mock you "static" class-level methods. For example, if your method was def self.foo and you called it via MyClass.foo, then allow(MyClass) is the way to go.
Your method is not a class-level method, it's an instance method. You invoke it by first creating an instead of MyClass and then calling the method on that instance. You need to use allow_any_instance_of to mock the method for all future instances of the class:
allow_any_instance_of(MyClass).to receive(....)
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.
I'm testing my module and I decided to test it versus anonymous class:
subject(:klass) { Class.new { include MyModule } }
MyModule uses method name inside klass. To let my specs work I need to stub this method name (which is unimplemented). So I wrote:
subject { klass.new }
allow(subject).to receive(:name).and_return('SOreadytohelp') }
but it raises:
RSpec::Mocks::MockExpectationError: #<#<Class:0x007feb67a17750>:0x007feb67c7adf8> does not implement: name
from spec-support-3.3.0/lib/rspec/support.rb:86:in `block in <module:Support>'
how to stub this method without defining it?
RSpec raises this exception because it is not useful to stub a method that does not exist on the original object.
Mocking methods is always error-prone because the mock might behave differently than the original implementation and therefore specs might be successful even if the original implementation would have returned an error (or does not even exist). Allowing to mock non-existing methods is just plain wrong.
Therefore I would argue that you should not try to bypass this exception. Just add a name method to your class that raises a clear exception if run outside of the test environment:
def self.name
raise NoMethodError # TODO: check specs...
end
subject(:klass) do
Struct.new(:name) do
include MyModule
end
end
http://ruby-doc.org/core-2.2.0/Struct.html
I think that if the test you're writing is focused on your MyModule module, and that module relies on an instance method in the class that it is mixed into, then I think that method should be mocked out in the anonymous class that you use when testing the module. For example:
module MyModule
def call_name
# expected implementation of #name to be
# in the class this module is mixed into
name
end
end
RSpec.describe MyModule do
let(:my_module_able) do
Class.new do
include MyModule
# We don't care what the return value of this method is;
# we just need this anonymous class to respond to #name
def name
'Some Name that is not SOReadytohelp'
end
end.new
end
describe '#call_name' do
let(:name) { 'SOReadytohelp' }
before do
allow(my_module_able).to receive(:name).and_return(name)
end
it 'returns the name' do
expect(my_module_able.call_name).to eq(name)
end
end
end
I got a model with a private method I'd like to spec with RSpec,
how do you usually do ?
Do you only test the method calling the private one ?
or also spec the private one ? if so, how do you do ?
I always take this approach: I want to test the public API my class exposes.
If you have private methods, you only call them from the public methods you expose to other classes. Hence, if you test that those public methods work as expected under all conditions, you have also proven that the private methods they use work as well.
I'll admit that I've come across some especially complex private methods. In that extreme case you want to test them, you can do this:
#obj.send(:private_method)
For the private methods that need code coverage (either temporarily or permanently), use the rspec-context-private gem to temporarily make private methods public within a context.
gem 'rspec-context-private'
It works by adding a shared context to your project.
RSpec.shared_context 'private', private: true do
before :all do
described_class.class_eval do
#original_private_instance_methods = private_instance_methods
public *#original_private_instance_methods
end
end
after :all do
described_class.class_eval do
private *#original_private_instance_methods
end
end
end
Then, if you pass :private as metadata to a describe block, the private methods will be public within that context.
class Example
private def foo
'bar'
end
end
describe Example, :private do
it 'can test private methods' do
expect(subject.foo).not eq 'bar'
end
end
create a dummy class and access private method using
.send(:private_method, args)
example
obj = Class.new { extend Classname } obj.send(:sum, 1,2)
obj = Class.new { extend Classname } obj.send(:sum)
If you're wanting to test an expectation on a private method, the accepted answer won't really work (at least not that I know of, so I'm open to correction on that point). What I've done instead is even filthier - in the test itself, just expose the method by redefining it:
def object_to_test.my_private_method
super
end
Works on Ruby 1.8, can't comment on any of the newer runtimes.