I’m not sure if I have a bug somewhere, or am just not using good practice. Suppose I have either of the following:
class ThingMailer < ApplicationMailer
def notify_of_thing
mail(subject: 'Thing has happened')
end
end
# ... and elsewhere...
class ThingDoer
def do_thing
ThingMailer.notify_of_thing.deliver_later(wait: 30.seconds)
end
end
or
class ThingWorker
include Sidekiq::Worker
def perform(name)
some_model.update(name: name)
end
end
# ... and elsewhere...
class ThingPerformer
def perform_thing
ThingWorker.perform_in(30.seconds, 'Bob')
end
end
And elsewhere I have a feature test (or other highish level test) which, in the normal course of things, causes ThingPerformer#perform_thing
or ThingDoer#do_thing
to be called. What’s the best practice for dealing with them in a test suite?
In my actual test suite, if I don’t just stub out one of the thread-launching methods, and if I’m not running Redis in the background while the tests run, I get the error Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED) (Redis::CannotConnectError)
.
In config/environments/production.rb
, we specify the cache store:
config.cache_store = :redis_store, ENV['REDIS_URL'], { expires_in: 90.minutes }
But our config/environments/test.rb
, we specify that the app shouldn’t perform caching (though presumably that’s not working, and maybe just fixing whatever’s causing this issue would be the answer to the first question?)
Here’s the test.rb
file:
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=3600'
}
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# config.active_job.queue_adapter = :test
# Fix the order in which test cases are executed.
# config.active_support.test_order = :sorted
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raises error for missing translations
config.action_view.raise_on_missing_translations = true
config.cache_store = :null_store
end
2
Answers
Sidekiq provides a testing guide.
If you want to test the behaviour of the job, use
Sidekiq::Testing.inline!
. If you just want to check the code without testing the job, useSidekiq::Testing.fake!
and check if jobs are enqueued.We use Resque in our project and our feature/system specs implement the following, which run and pass without
redis-server
running:Scenario
A user signs up for a new account, and when the form submits we have the background job send a confirmation email to the provided email address.
Basically we are stubbing the
enqueue
method with no return value, so it is extremely important to unit test what the job actually does further down the line.Relevant Documentation
When the user clicks submit it posts the form, and as you’d expect that would bubble down and eventually hit a background job, adding to the queue, that calls a service, and then sends the email.
I’ll leave the below examples for reference of the flow.
Our (super simple example) form class might look like this:
I’d then unit test the form class by simply checking:
Then the Job class might look something like:
Which, if I’m being honest with you, I wouldn’t bother testing at all really.
Then the mailer class would be something in the vein of:
NB: I’m a big fan of the facade pattern as I find it much easier to collate the related services to a certain action, and also easier to test, but you could very easily skip it and just set
@user ||= User.find(user_id)
and use@user.email
as the recipient and a string for the subject.Dealer’s choice here.
If you went down the facade route it might look like:
I18n.t('users.new_account_confirmation.subject')
= ‘Please confirm your email address to complete your account registration for YOURAPPNAME’Then it’s just another simple unit test.
I’ve used factories here as that’s just what I’m used to. But hopefully, this provides some form of help to you.
I’d also recommend looking into making the transition to system specs if that’s a possibility in your position. Speaking from experience, our team saw a dramatic decrease in flaky specs, and js (mainly ajax) related troubles we experienced back when we wrote features after making the change.
To play devil’s advocate though, system specs take MUCH longer to run on our CI than our features did (especially with
js: true
turned on), so it’s not all sunshine and daisies in the system world, unfortunately.