I have a class for example:
class BackgroundJob
def run
pre_processing
processing
end
def preprocessing
end
def processing
end
end
So my code will run is: BackgroundJob.new.run. In my rspec, I want to do "something" before calling processing method. How can I do this in rspec.
Thanks
In RSpec, as anywhere else, this can be done by using Module#prepend:
BackgroundJob.prepend(Module.new do
def processing
puts "do stuff"
super
end
end)
This approach has a drawback: the modified class will stay with the module prepended, there is no way to “unprepend” already prepended module.
The other approach would be to use Flexmock’s pass_thru. AFAIK, pass_thru might only append the additional code to the method, hence one should flexmock preprocessing method:
BackgroundJob.should_receive(:preprocessing).pass_thru do |job|
job.tap do |job|
puts "do_stuff"
end
end
Related
Below is passing!
Controller code:
class OrdersController
def create
...
#order.save
end
end
Spec code:
describe OrdersController do
it "should call save method" do
Order.any_instance.should_receive(:save)
post :create
end
end
But if only it were that easy... I have some custom job objects that are executed after the save, so the code actually looks like this:
Controller code:
class OrdersController
def create
...
#order.save
RoadrunnerEmailAlert.new.async.perform(#order.id, true)
CalendarInvite.new.async.perform(#order.id)
RoadrunnerTwilioAlert.new.async.perform(#order.id)
end
end
I would love to test that the custom objects are receiving the chain of methods with the right parameters, but not sure how, short of creating something in the spec code like this:
before do
class RoadrunnerEmailAlert
def async
end
end
end
But that's so contrived, it certainly isn't right... advice appreciated!
In case this helps other people... this is a very comprehensive answer.
Context & design notes
The async framework is Sucker Punch gem
(http://brandonhilkert.com/blog/why-i-wrote-the-sucker-punch-gem/).
Back then, this was the easiest thing for me to use after looking at
Delayed Job, Sidekick, etc
Basically it works like this: in Controller reference a Job that then references anything else (in my case, some POROs)
If I were really rigidly testing, I'd want to test that A) the Controller calls the Job appropriately and passes the right parameters, and B) the Job calls the appropriate POROs and passes the right parameters. But instead, I just tested that the Controller calls the appropriate POROs and passes the right parameters, i.e., the Jobs are already working.
Controller code
#order.save
RoadrunnerEmailAlert.new.async.perform(#order.id, true)
CalendarInvite.new.async.perform(#order.id)
RoadrunnerTwilioAlert.new.async.perform(#order.id)
Job code
# app/jobs/roadrunner_email_alert.rb
class RoadrunnerEmailAlert
include SuckerPunch::Job
def perform(order_id, require_tos)
ActiveRecord::Base.connection_pool.with_connection do
OrderMailer.success_email(order_id, require_tos).deliver
end
end
end
# app/jobs/calendar_invite.rb
class CalendarInvite
include SuckerPunch::Job
def perform(order_id)
ActiveRecord::Base.connection_pool.with_connection do
CreateCalendar.new(order_id).perform
end
end
end
# app/jobs/roadrunner_twilio_alert.rb
class RoadrunnerTwilioAlert
include SuckerPunch::Job
def perform(order_id)
ActiveRecord::Base.connection_pool.with_connection do
CreateAlert.new(order_id).perform
end
end
end
Test code
The really big thing here that I don't know why I keep forgetting (but only in testing) is class vs. instance of class. For the POROs, since I'm instantiating the object, I needed to test 2 different "layers" (first that the object is instantiated appropriately, second that the instantiated object is acted upon appropriately).
require 'sucker_punch/testing/inline'
describe "Controller code" do
before do
OrderMailer.any_instance.stub(:success_email)
mock_calendar = CreateCalendar.new(1)
CreateCalendar.stub(:new).and_return(mock_calendar)
CreateCalendar.any_instance.stub(:perform)
mock_alert = CreateAlert.new(1)
CreateAlert.stub(:new).and_return(mock_alert)
CreateAlert.any_instance.stub(:perform)
end
it "should call appropriate async jobs" do
expect_any_instance_of(OrderMailer).to receive(:success_email).with(1, true)
expect(CreateCalendar).to receive(:new).with(1)
expect_any_instance_of(CreateCalendar).to receive(:perform)
expect(CreateAlert).to receive(:new).with(1)
expect_any_instance_of(CreateAlert).to receive(:perform)
post :create
end
end
I have about 20 different Active Jobs which I now realise are each going to need a before_perform method in which to set PaperTrail content outside the context of the controller.
I was planning on putting this before_perform method in a helper and then including the helper in each of the jobs but I am getting an error:
undefined method `before_perform' for MyApp:JobHelpers:Module
I am thinking that this is because the module in question is just that, a module and not an Active Job. How can I avoid repeating the same 4 line before_perform method in each of my Active Jobs?
Job_helper:
module MyApp
module JobHelpers
before_perform do |job|
# stuff to do
end
end
end
The_job:
require 'my_app/job_helpers'
class TheJob < ActiveJob::Base
include MyApp::JobHelpers
# Do more stuff
end
Rewrite your helper like this:
module MyApp
module JobHelpers
extend ActiveSupport::Concern
included do
# inside this you can call ActiveJob helpers
before_perform do
# stuff to do
end
end
end
end
I used an included callback to achieve my desired goal. I found a better description of the included callback than I could ever give in another answer here.
While other answers were similar, please find the solution that worked for me below:
module MyApp
module JobHelpers
def self.included(job_class)
job_class.before_perform do |job|
# work to be completed
end
end
end
end
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.
I am using jruby to run bunch of ruby scripts, though I am using pure ruby part of it.
It sometimes gets difficult to follow from output what exactly is happening or where exactly something went wrong.
I wanted to get something like this in my std out for every method:
entered in method A
out of method A
Now I can surely go and put those comments in every method ..which feels very wrong. Is there a way to run ruby in a little verbose more to get this information in my log. Hopefully I would avoid using a lot of gems etc .. since these are on some managed servers and I will have to spend some time to just get more s/f on it. Hoping something would be avail as part of jruby itself
Thanks!
You could use this code:
module InOutHook
module ClassMethods
def setup_hooks(*syms)
syms.each do |sym| # For each symbol
str_id = "__#{sym}__hooked__"
unless private_instance_methods.include?(str_id)
alias_method str_id, sym # Backup original method
private str_id # Make backup private
define_method sym do |*args| # Replace method
puts ">>> #{self.class}\##{sym} >>>"
ret = __send__ str_id, *args # Invoke backup
puts "<<< #{self.class}\##{sym} >>>"
ret
end
end
end
end
end
def InOutHook.included(base)
base.extend(ClassMethods)
end
end
class TestClass
def test1
puts "test!"
end
def test2(v)
puts "Value is #{v}"
end
include InOutHook
setup_hooks(:test1, :test2)
end
# works on existing classes too:
class Array
include InOutHook
setup_hooks(:[])
end
tc = TestClass.new
tc.test1
tc.test2(10)
ary = [1,2,3]
puts ary[1..2]
In case you want to add a hoot to every method, just add a splat asterisk:
setup_hooks(*[].methods)
Given the Thread class with it current method.
Now inside a test, I want to do this:
def test_alter_current_thread
Thread.current = a_stubbed_method
# do something that involve the work of Thread.current
Thread.current = default_thread_current
end
Basically, I want to alter the method of a class inside a test method and recover it after that.
I know it sound complex for another language, like Java & C# (in Java, only powerful mock framework can do it). But it's ruby and I hope such nasty stuff would be available
You might want to take a look at a Ruby mocking framework like Mocha, but in terms of using plain Ruby it can be done using alias_method (documentation here) e.g.
beforehand:
class Thread
class << self
alias_method :old_current, :current
end
end
then define your new method
class Thread
def self.current
# implementation here
end
end
then afterwards restore the old method:
class Thread
class << self
alias_method :current, :old_current
end
end
Update to illustrate doing this from within a test
If you want to do this from within a test you could define some helper methods as follows:
def replace_class_method(cls, meth, new_impl)
cls.class_eval("class << self; alias_method :old_#{meth}, :#{meth}; end")
cls.class_eval(new_impl)
end
def restore_class_method(cls, meth)
cls.class_eval("class << self; alias_method :#{meth}, :old_#{meth}; end")
end
replace_class_method is expecting a class constant, the name of a class method and the new method definition as a string. restore_class_method takes the class and the method name and then aliases the original method back in place.
Your test would then be along the lines of:
def test
new_impl = <<EOM
def self.current
"replaced!"
end
EOM
replace_class_method(Thread, 'current', s)
puts "Replaced method call: #{Thread.current}"
restore_class_method(Thread, 'current')
puts "Restored method call: #{Thread.current}"
end
You could also write a little wrapper method which would replace a method, yield to a block and then ensure that the original method was reinstated afterwards e.g.
def with_replaced_method(cls, meth, new_impl)
replace_class_method(cls, meth, new_impl)
begin
result = yield
ensure
restore_class_method(cls, meth)
end
return result
end
Inside your test method this could then be used as:
with_replaced_method(Thread, 'current', new_impl) do
# test code using the replaced method goes here
end
# after this point the original method definition is restored
As mentioned in the original answer, you can probably find a framework to do this for you but hopefully the above code is interesting and useful anyway.