Debugging Delayed_Jobs - ruby-on-rails

I'm looking to debug a delayed jobs class. First off I added the config/initializers/delayed_job_config to move my logging to my log/production.rb file.
Delayed::Job.destroy_failed_jobs = false
Delayed::Worker.logger = Rails.logger
Then in the actual file I'm doing in the actual file
class TestJob < Struct.new()
logger.debug("test logging")
end
The log isn't showing anything in it. Anyone have any ideas?

I've had luck with rending the backtrace of the error to an email, which at least gives me some context of when / how the delayed job is failing:
Here is an example:
result = Delayed::Job.work_off
unless result[1].zero?
ExceptionMailer.deliver_exception_message("[Delayed Job Failed] Error id: #{Delayed::Job.last.id}", Delayed::Job.last.last_error)
end
If you just want to write tests for your Delayed::Job tasks here is the approach I have taken. I will stub out the perform task with the expectations from the various scenarios and then test how Delayed::Job handles those results. Here is an example of how I used Delayed::Job to sync with a remote CMS nightly.
it "should sync content from the remote CMS" do
CMSJobs::Sync.module_eval do
def perform
url.should == "http://someurl.com/tools/datafeed/resorts/all"
Resort.sync_resorts!([{'id' => 1, 'name' => 'resort foo'}, { 'id' => 2, 'name' => 'resort bar' }])
end
end
lambda do
Resort.sync_all!
end.should change(Delayed::Job, :count)
lambda do
Delayed::Job.work_off
end.should change(Resort, :count).by(2)
# It should now delete a resort if it doesn't appear in the next CMS feed.
lambda do
Resort.sync_resorts!([{ 'id' => 2, 'name' => 'resort bar' }])
end.should change(Resort, :count).by(-1)
end

Have you tried to narrow it down to whether this is a problem with the delayed job not getting fired or the logger config not working for you? What if you replace the logger.debug call with a puts?

Related

Hook before all delyed job success callback to save successfully completed jobs

Objective:
To save successfully completed jobs
Methods used
Have looked at this answer which tells how to save completed jobs and this answer which suggests to create a plugin which will be executed after one of lifecycle events.
Problem
There are following lifecycle events, and applicable arguments as present in code:
:enqueue => [:job],
:execute => [:worker],
:loop => [:worker],
:perform => [:worker, :job],
:error => [:worker, :job],
:failure => [:worker, :job],
:invoke_job => [:job]
I had expected there will be some event corresponding to success, I am not getting which one will be executed after job is successful.
Some Code
With the help of above tow answers, I have created this plugin to save completed job and put in config/initializers/save_completed_delayed_job.rb
module Delayed
module Plugins
class SaveCompletedDelayedJobPlugin < Plugin
callbacks do |lifecycle|
# see below for list of lifecycle events
lifecycle.after(:perform) do |job|
p "This should be ran after success so that I can save the job in different table"
CompletedDelayedJob.create({
priority: job.priority,
attempts: job.attempts,
handler: job.handler,
last_error: job.last_error,
run_at: job.run_at,
failed_at: job.failed_at,
completed_at: DateTime.now,
queue: job.queue
})
end
end
end
end
end
Delayed::Worker.plugins << Delayed::Plugins::SaveCompletedDelayedJobPlugin
Why I need plugin
I can not use callback methods as I don't have dedicated job classed, I am using following code to queue job.
handle_asynchronously :deliver_delayed, :queue => 'my_q'

How to test correctness of arguments sent to external API

I've external API endpoint, let's say: http://www.fake_me_hard.com/api. I would like to make some calls to this from my app.
Endpoint accepts following structure as argument:
{
:amount => amount,
:backurl => root_path,
:language => locale,
:orderid => order_id,
:pm => payment_method,
:accept_url => "/payment/success",
:exception_url => "/payment/failure",
}
For collecting this hash is responsible method EndpointRequestCollector.give_me_hash.
How I should test if give_me_hash returns proper structure ?
I can use the same strategy for creating this structure in specs and class as well so:
class EndpointRequestsCollector
def self.give_me_hash
{
#....collecting hash #1
}
end
end
describe EndpointRequestCollector do
context '.give_me_hash' do
it 'returns proper structure' do
expect(described_class.give_me_hash).to eq(
{
#... collecting hash #2
}
)
end
end
end
...but it would be repeating the same code in 2 places, and won't test anything.
Do you know any good approach to this problem ?
This is the way that i usually test my json api's:
If you just want to test the format, you can use include matcher:
%w(my awesome keys).each do |expected_key|
expect(described_class.give_me_hash.keys).to include(expected_key)
end
By doing this, you have the guarantee that the formar is correct, until someone break you method.
If you want to test the returned values, you can use something like that:
let(:correct_value) { 42 }
it 'must have correct value' do
expect(described_class.give_me_hash[key]). to eq correct_value
end
But i recomment you to separate this the logic to get the value to another method, and make another test just for it.
Perhaps:
let(:args) {["amount", "backurl", "language", "orderid", "pm", "accept_url", "exception_url"]}
#...
it 'returns proper structure' do
described_class.give_me_hash.each_key do |key|
expect(key).to satisfy{|key| args.include?(key)}
end
end

Testing that rails model validates date with RSpec

I'm a bit new to Rails and Rspec and as such I'm not sure how to test that date time validations are correct in my model.
I've made a model Event that has start and end times and there's a few imporant conditions on these such as a start time cannot be in the past and the end time must be after the start time.
To ensure these validations I'm using ValidatesTimeliness https://github.com/adzap/validates_timeliness
My model is as follows:
class Event < ActiveRecord::Base
...
validates_datetime :start_date,
:after => :now,
:after_message => "Event cannot start in the past"
validates_datetime :end_date,
:after => :start_date,
:after_message => "End time cannot be before start time"
end
In my RSpec test I have:
describe Event do
let(:event) { FactoryGirl.build :event }
subject { event }
context "when start_date is before the current time" do
it {should_not allow_value(1.day.ago).
for(:start_date)}
end
context "when end_date is before or on start date" do
it {should_not allow_value(event.start_date - 1.day).
for(:end_date)}
it {should_not allow_value(event.start_date).
for(:end_date)}
end
context "when the end_date is after the start_date" do
it {should allow_value(event.start_date + 1.day).
for(:end_date)}
end
end
However this doesn't really test that my start date had to be before the exact date time.
For example if I'd accidentally used :today instead of :now in my model, these tests would also pass.
I read online that there used to be an RSpec matcher called validate_date (http://www.railslodge.com/plugins/1160-validates-timeliness) which would be exactly what I'm looking for but as far as I can tell it's been removed.
My question is how can I improve my tests, do I need to add tests that try the smallest amount of time (i.e. a ms) to ensure the pass/fail accordingly or is there a better way to do it?
Thanks in advance!
You could work with valid? and errors.messages:
Build anEvent that passes validation except for your start_date and end_date
Set the start_date and end_date in the right order, and assert that event.valid? is true
Set the start_date and end_date in the wrong order, and assert that it is not valid? and that event.errors.messages includes the right validation errors. (Note, you have to call event.valid? before checking event.errors.messages, otherwise they will be empty)
Example for valid? and errors.messages:
user = User.new
user.errors.messages #=> {} # no messages, since validations never ran
user.valid? # => false
user.errors.messages #=> {:email=>["can't be blank"]}
user.email = "foo#bar.com"
user.valid? #=> true
user.errors.messages #=> {}
Try this
validates_date :end_time, :after => [:start_time, Proc.new {1.day.from_now_to_date}]
validates_date :start_time, :after => Time.now

Simple syntax for testing Validation errors

I'm looking for clean and short code to test validations in Rails Unittests.
Currently I do something like this
test "create thing without name" do
assert_raise ActiveRecord::RecordInvalid do
Thing.create! :param1 => "Something", :param2 => 123
end
end
I guess there is a better way that also shows the validation message?
Solution:
My current solution without an additional frameworks is:
test "create thing without name" do
thing = Thing.new :param1 => "Something", :param2 => 123
assert thing.invalid?
assert thing.errors.on(:name).any?
end
You don't mention what testing framework that you're using. Many have macros that make testing activerecord a snap.
Here's the "long way" to do it without using any test helpers:
thing = Thing.new :param1 => "Something", :param2 => 123
assert !thing.valid?
assert_match /blank/, thing.errors.on(:name)
In newer versions of Rails (v5) with MiniTest
test "create thing without name" do
thing = Thing.new :param1 => "Something", :param2 => 123
assert thing.invalid?
assert thing.errors.added? :name, :blank
end
https://devdocs.io/rails~5.2/activemodel/errors
I'm using Rails 2.0.5, and when I want to assert that a model will fail validation, I check the errors.full_messages method, and compare it to an array of expected messages.
created = MyModel.new
created.field1 = "Some value"
created.field2 = 123.45
created.save
assert_equal(["Name can't be blank"], created.errors.full_messages)
To assert that validation succeeds, I just compare to an empty array. You can do something very similar to check that a Rails controller has no error messages after a create or update request.
assert_difference('MyModel.count') do
post :create, :my_model => {
:name => 'Some name'
}
end
assert_equal([], assigns(:my_model).errors.full_messages)
assert_redirected_to my_model_path(assigns(:my_model))
For those using Rails 3.2.1 and up, I prefer using the added? method:
assert record.errors.added? :name, :blank
I use a test helper that looks like this:
def assert_invalid(record, options)
assert_predicate record, :invalid?
options.each do |attribute, message|
assert record.errors.added?(attribute, message), "Expected #{attribute} to have the following error: #{message}"
end
end
Which allows me to write tests like this:
test "should be invalid without a name" do
user = User.new(name: '')
assert_invalid user, name: :blank
end
Try also accept_values_for gem.
It allows to do something like this:
describe User do
subject { User.new(#valid_attributes)}
it { should accept_values_for(:email, "john#example.com", "lambda#gusiev.com") }
it { should_not accept_values_for(:email, "invalid", nil, "a#b", "john#.com") }
end
In this way you can test really complicated validations easily
You could give the rspec-on-rails-matchers a try. Provides you with syntax like:
#thing.should validates_presence_of(:name)
Not sure when it was added but the #where method makes it easy to target a specific error without having to rely on the text of the message.
refute #thing.valid?
assert #thing.errors.where(:name, :invalid).present?

How can I add multiple should_receive expectations on an object using RSpec?

In my Rails controller, I'm creating multiple instances of the same model class. I want to add some RSpec expectations so I can test that it is creating the correct number with the correct parameters. So, here's what I have in my spec:
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => #user.id, :position_id => 1, :is_leader => true)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "2222", :position_id => 2)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "3333", :position_id => 3)
Bandmate.should_receive(:create).with(:band_id => #band.id, :user_id => "4444", :position_id => 4)
This is causing problems because it seems that the Bandmate class can only have 1 "should_receive" expectation set on it. So, when I run the example, I get the following error:
Spec::Mocks::MockExpectationError in 'BandsController should create all the bandmates when created'
Mock 'Class' expected :create with ({:band_id=>1014, :user_id=>999, :position_id=>1, :is_leader=>true}) but received it with ({:band_id=>1014, :user_id=>"2222", :position_id=>"2"})
Those are the correct parameters for the second call to create, but RSpec is testing against the wrong parameters.
Does anyone know how I can set up my should_receive expectations to allow multiple different calls?
Multiple expectations are not a problem at all. What you're running into are ordering problems, given your specific args on unordered expectations. Check this page for details on ordering expectations.
The short story is that you should add .ordered to the end of each of your expectations.
Mock Receive Counts
my_mock.should_receive(:sym).once
my_mock.should_receive(:sym).twice
my_mock.should_receive(:sym).exactly(n).times
my_mock.should_receive(:sym).at_least(:once)
my_mock.should_receive(:sym).at_least(:twice)
my_mock.should_receive(:sym).at_least(n).times
my_mock.should_receive(:sym).at_most(:once)
my_mock.should_receive(:sym).at_most(:twice)
my_mock.should_receive(:sym).at_most(n).times
my_mock.should_receive(:sym).any_number_of_times
Works for rspec 2.5 too.

Resources