Verify Recurring Job Schedules With a Spec
published 03/03/26
My Ruby on Rails app Monolithium uses Solid Queue for background jobs but also has some recurring jobs. When I migrated to Solid Queue I typoed the schedule strings for these recurring jobs and then had to make a PR to fix them. This felt like a silly mistake and seemed like something that could have easily been prevented. I made a mental note but moved on with my life.
Then this verification topic came up at work this week. I addressed it there and then took the opportunity to apply it to my Rails app as well.
Solid Queue Recurring Job Schedules
The recurring jobs that are managed by Solid Queue are configured with a YAML file that looks something like this:
# config/recurring.yml
production:
name_of_job:
class: MyBestJob
schedule: every 10 minutes
The top-most key is the environment to run so here I'm using production but
you could also specify jobs just for development but I never have. From there
the next level of key is the name of the recurring job or task that you want to
define. Then inside that key you specify the class and schedule and any
additional configuration. What I've specified here is that every 10 minutes the
MyBestJob class should be enqueued and worked.
For that schedule string here's what the Solid Queue docs have to say:
Each task needs to have also a schedule, which is parsed using Fugit, so it accepts anything that Fugit accepts as a cron.
Ah ha so now we know how these schedule strings are used - they are passed off to Fugit and whatever that gem accepts is allowed here.
Fugit Parsing
The Fugit gem can parse a lot of different types of inputs. It can take something high level like "every 10 minutes" but then it can also take low level cron strings like "5 4 * * sun" which I just randomly generated from crontab.guru. That range of inputs then is inherited by Solid Queue. Neat!
Writing a Spec to Verify Schedule Strings
With all this in mind the idea for this spec is to read in the config file, grab
the schedule strings, and then parse them with Fugit. I learned from the Fugit
docs that I could use Fugit.parse for this and it will return nil if the input
string cannot be parsed.
I made Verify recurring job schedules with a spec and here's what I ended up with:
describe "recurring schedules" do
it "only has valid schedule strings" do
config_path = Rails.root.join("config/recurring.yml")
recurring_config = ActiveSupport::ConfigurationFile.parse(config_path)
tasks = recurring_config.values.map(&:values).flatten
schedules = tasks.map { |task| task["schedule"] }
schedules.each do |schedule|
expect(Fugit.parse(schedule)).to_not eq nil
end
end
end
What this spec does is use the ActiveSupport::ConfigurationFile helper to grab
the contents of the recurring jobs config file. That will already have converted
it into a hash so then I deconstruct that hash and pluck out the values of the
schedule keys and make an array out of that. Then I loop over that array and
send each string to the Fugit.parse method with an assertion that the result
is not nil. If the config file is valid then the test passes. If I edit the file
and use a schedule that is invalid then the test fails. Confidence restored!
Conclusion
This is exactly what I wanted - a safety net that will ensure I do not repeat the mistake of using an incorrect schedule string. I'm not fully satisfied though. There are two things bothering me: the lack of help when the test fails and that Solid Queue should be helping me here.
For that first one what I noticed is that if I intentionally make the test fail then the error message is all about how something is nil. What I think a better test would do is give me which schedule broke. I bet I could write something like a custom matcher for this but who has the time??
Then the other thing is that honestly why doesn't Solid Queue give me something to work with here? If I had the idea for this spec then I'm sure others have as well. In my imagination there are all these teams using Solid Queue and writing the exact same type of test - what a waste! Many Rails related gems have a test helper or test mode or something like that. I guess if I really cared about this what I would do is open up an issue or PR on that project but who has the time??