Unable to set session in RSpec - ruby-on-rails

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.

Related

Calling rspec methods from different file

I am trying to write a class in my code to wrap some of the RSpec calls. However, whenever I try to access rspec things, my class simply doesn't see the methods.
I have the following file defined in spec/support/helper.rb
require 'rspec/mocks/standalone'
module A
class Helper
def wrap_expect(dbl, func, args, ret)
expect(dbl).to receive(func).with(args).and_return(ret)
end
end
end
I get a NoMethodError: undefined method 'expect', despite requiring the correct module. Note that if I put calls to rspec functions before the module, everything is found correctly.
I've tried adding the following like to my spec_helper.rb:
config.requires << 'rspec/mocks/standalone'
But to no avail.
I managed to use class variables in my class and passing the functions through from the global context, but that solution seems quite extreme. Also I was able to pass in the test context itself and storing it, but I'd rather not have to do that either.
expect functions by default is associated with only rspec-core methods like it before . If you need to have expect inside a method, you can try adding the Rspec matcher class in the helper file.
include RSpec::Matchers
that error because the self which call expect is not the current rspec context RSpec::ExampleGroups, you could check by log the self
module A
class Helper
def wrap_expect(dbl, func, args, ret)
puts self
expect(dbl).to receive(func).with(args).and_return(ret)
end
end
end
# test case
A::Helper.new.wrap_expect(...) # log self: A::Helper
so obviously, A::Helper does not support expect
now you have 2 options to build a helper: (1) a module or (2) a class which init with the current context of test cases:
(1)
module WrapHelper
def wrap_expect(...)
puts self # RSpec::ExampleGroups::...
expect(...).to receive(...)...
end
end
# test case
RSpec.describe StackOverFlow, type: :model do
include WrapHelper
it "...." do
wrap_expect(...) # call directly
end
end
(2)
class WrapHelper
def initialize(spec)
#spec = spec
end
def wrap_expect(...)
puts #spec # RSpec::ExampleGroups::...
#spec.expect(...).to #spec.receive(...)...
end
end
# test case
RSpec.describe StackOverFlow, type: :model do
let!(:helper) {WrapHelper.new(self)}
it "...." do
helper.wrap_expect(...)
end
end

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

Rspec:: allow every instance to receive a message

I want to mock a method for every instance of a class.
if I allow_any_instance_of then it works great if instance_count = 1
However if I have many instances of the same class the second instance isn't caught by the mock.
I'm attempting to get a pile of tokens from different sites. But during testing I don't really need "real" tokens. So I plan to mock get_token to return '1111'.
class Foo
def children
[Bar.new, Bar.new] #....
end
def get_tokens
children.map(&:get_token) || []
end
end
so now how do I not mock out the get_tokens?
How about solution like this:
require "spec_helper"
require "ostruct"
class Bar
def get_token
("a".."f").to_a.shuffle.join # simulating randomness
end
end
class Foo
def children
[Bar.new, Bar.new, Bar.new]
end
def get_tokens
children.map(&:get_token) || []
end
end
RSpec.describe Foo do
before do
allow(Bar).to receive(:new).and_return(OpenStruct.new(get_token: "123"))
end
it "produces proper list of tokens" do
expect(Foo.new.get_tokens).to eq ["123", "123", "123"]
end
end
We're stubbing new method on Bar to return something that quacks with get_token (so it behaves like Bar), and it returns a fixed string. This is something you can relay on.
Hope that helps!

RSpec: stubbing Rails.application.config value doesn't work when reopening classes?

I have an option defined in application config. My class I want to test is defined in a gem (not written by me). I want to reopen the class:
Myclass.class_eval do
if Rails.application.config.myoption=='value1'
# some code
def self.method1
end
else
# another code
def self.method2
end
end
end
I want to test this code using RSpec 3:
# myclass_spec.rb
require "rails_helper"
RSpec.describe "My class" do
allow(Rails.application.config).to receive(:myoption).and_return('value1')
context 'in taxon' do
it 'something' do
expect(Myclass).to respond_to(:method1)
end
end
end
How to stub application config value before running the code which reopens a class.
Wow, this have been here for a long time, but in my case what I did was:
allow(Rails.configuration.your_config)
.to receive(:[])
.with(:your_key)
.and_return('your desired return')
Specs passing and config values stubed correctly. =)
Now, the other thing is about your implementation, I think it would be better if you defined both methods and inside from a run or something you decided wich one to execute. Something like this:
class YourClass
extend self
def run
Rails.application.config[:your_option] == 'value' ? first_method : second_method
end
def first_method
# some code
end
def second_method
# another code
end
end
Hope this helps.
Edit: Oh yeah, my bad, I based my answer on this one.

How to add attribute to existing Notifications payload?

In Rails notifications, I am subscribing to "process_action.action_controller", and would like to add more attributes to the payload. How can I do that?
I have tried using append_info_to_payload, but this seems to do nothing.
module AppendExceptionPayload
module ControllerRuntime
extend ActiveSupport::Concern
protected
def append_info_to_payload(payload)
super
payload[:happy] = "HAPPY"
end
end
end
The subscription and above code is in a Rails engine, so this is where I make the call to add it:
require 'append_exception_payload'
module Instrument
class Engine < ::Rails::Engine
ActiveSupport.on_load :action_controller do
include AppendExceptionPayload::ControllerRuntime
end
end
end
After putting up the bounty, I found a solution myself. Rails handles this really cleanly.
Basically, the append_info_to_payload method is meant exactly for this.
So to include session information and signed_in user information I added this to my application_controller.rb:
def append_info_to_payload(payload)
super
payload[:session] = request.session_options[:id] rescue ""
payload[:user_id] = session[:user_id] rescue "unknown"
end
So i jumped in and had a look at the api for the process_action method (private) and the append_info_to_payload instance method (public) and the proccess action method seems to call append_info_to_payload in its code like so:
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
result = super
payload[:status] = response.status
append_info_to_payload(payload)
result
end
and append_info_to_payload works something like this
def append_info_to_payload(payload) #:nodoc:
payload[:view_runtime] = view_runtime
end
I can suggest trying payload[:view_runtime] instead of payload[:happy] or trying to use payload[:status]
Let me know how you get on and I will try help more, unfortunately there is really no documentation for this stuff.

Resources