Calling rspec methods from different file - ruby-on-rails

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

Related

Rspec "NoMethodError" from nested module

I'm running into a weird error:
Class:
module AnimalSanctuary
module AnimalInspector
class AnimalPicker
def initialize(number_of_animals, ids)
#number_of_animals = number_of_animals
#ids = ids
end
...
def pick_animals(animal)
end
end
end
test:
require 'rails_helper'
RSpec.describe AnimalSanctuary::AnimalInspector::AnimalPicker do
describe ".pick_animals" do
context "pick an available animal" do
it "returns animal name" do
expect(AnimalSanctuary::AnimalInspector::AnimalPicker.pick_animals("Dog")).to eq("Dog")
end
end
end
end
I get the following error:
NoMethodError:
undefined method `pick_animals' for AnimalSanctuary::AnimalInspector::AnimalPicker:Class
Rspec calls the class but not the method which has stumped me. Am I doing something wrong?
The definition of pick_animals is an instance method.
To call it, you will need to instantiate an object of the class using the new method as shown below. I have passed in random values to your initializer (1, [1,2]) however you can set them as you like.:
number_of_animals = 1
ids = [1,2]
AnimalSanctuary::AnimalInspector::AnimalPicker.new(number_of_animals, ids).pick_animals("Dog")
Otherwise, to call it the way you are calling it, you will need to redefine it as a class method by using self.pick_animals as shown below:
module AnimalSanctuary
module AnimalInspector
class AnimalPicker
...
def self.pick_animals(animal)
end
end
end
yeah pick_animals is an instance method.
you can use the following in your rspec
expect_any_instance_of(nimalSanctuary::AnimalInspector::AnimalPicker).to receive(:pick_animals).with("dogs").to_eq("Dog")
Hope this helps

Stub unimplemented method in rspec

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

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.

RSpec page variable

How is it that rspec feature tests implicitly know to use methods such as find, within, and fill_in from the page object?
I've written a helper class for some of my rspec tests and wanted to use those methods, and realized that I needed to pass the page object into the method, and then use page.find and the like.
RSpec achieves this by including Capybara::DSL in those cases where it wants those methods available. The module is pretty elegant, if you want to take a look at https://github.com/jnicklas/capybara/blob/f83edc2a515a3a4fd80eef090734d14de76580d3/lib/capybara/dsl.rb
suppose you want to include the following module:
module MailerMacros
def last_email
ActionMailer::Base.deliveries.last
end
def reset_email
ActionMailer::Base.deliveries = []
end
end
to include them, just call config.include(MailerMacros), like this:
RSpec.configure do |config|
config.include(MailerMacros)
end
now, you should be able to call reset_email() & last_email instead of MailerMacros::reset_email().

how to create rspec method which takes object argument

I am just trying to create a simple rspec method that takes an object as an argument. I can do something like the following:
def check_it(p)
before { visit p.full_path }
page.should have_title("sample title")
p.children.published.each do |pp|
check_it(pp)
end
end
so far I have not even figured out where I can put this code to avoid:
undefined method `check_it'
error, unless I put it with the other rails helpers at which point I get an
undefined method `before' for main:Object (NoMethodError)
Some help getting off the ground would be greatly appreciated.
You can add it in various places within the rspec file outside of the example. E.g.:
def check_it(p); puts p; end
describe 'foo' do
def check_it2(p); puts p; end
context 'bar' do
def check_it3(p); puts p; end
it 'does something' do
check_it 'hello'
check_it2 'world'
check_it3 'wide'
end
end
end
There are also ways to add it outside of the rspec file, e.g. put it in a file in spec/support and include it into the target rspec file.
Don't know how other people do it but I add a directory to spec and call it support because spec_helper includes this line:
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
Meaning anything in the support directory will be included in each run.
So you can just place this specific method directly in a file in spec/support and it will be callable or you can include it in a module and then include that module where you need it.

Resources