How to TDD ActiveJob in Rails 5 with RSpec

Ministry of Velocity
Ministry of Velocity
3 min readJan 17, 2017

--

Here at Ministry of Velocity, we use ActiveJob (and Sidekiq) for basically everything. Emails? Yep. SMS? You should see how many texts we send. Basically anything involving HTTP requests? OMG YISS. We think you should use ActiveJob and Sidekiq, too.

So, let’s say you want to do this for your application. You’ll probably want to do the first half of what we do: write a failing spec for the outside.

# spec/features/annoying_your_team_spec.rb
require 'rails_helper'
RSpec.describe 'Annoying your team', type: :feature do
scenario 'posts dramatic messages to slack' do
fill_in 'Message', with: 'can you just'
expect {
click_on 'Send'
}.to have_enqueued_job(SlackAnnoyerJob).with('can you just')
end
end

In order to run this spec, you’ll need to add a new support helper to RSpec:

# spec/support/activejob.rb
require 'sidekiq/testing'
RSpec.configure do |config|
config.include ActiveJob::TestHelper
config.before(:all) do
Sidekiq::Testing.fake!
end
config.before(:each) do
Sidekiq::Worker.clear_all
end
end

If you run in to trouble with that with matcher — usually because your job arguments require a little more effort to inspect — you can pass a block instead of arguments:

fill_in 'Message', with: "someone tell #{email}"
expect {
click_on 'Send'
}.to have_enqueued_job(SlackAnnoyerJob).with { |text|
expect(text).to include(email)
}

But wait, there’s more! When you enqueue two jobs back-to-back, you may want to check both of their arguments. Not to worry, here’s how to really get into the guts of the queue:

fill_in 'Message', with: "it's monday everyone"
click_on 'Send'
fill_in 'Message', with: "no wait tuesday"
click_on 'Send'
expect(queue_adapter.enqueued_jobs.first[:args].first).to
include('monday')
expect(queue_adapter.enqueued_jobs.second[:args].first).to
include('tuesday')

In the interest of brevity, let’s skip over the controller specs. Here’s the job’s spec:

# spec/jobs/slack_annoyer_job_spec.rb
require 'rails_helper'
RSpec.describe SlackAnnoyerJob, type: :job do
before do
allow(Rails.application.config).to receive(:slack_webhook_url) {
'http://example.com/webhook'
}
end
subject(:job) { SlackAnnoyerJob.new } describe '#perform' do
let(:uri) { URI.parse('http://example.com/webhook') }
it 'sends a slack notification' do
expect(Net::HTTP).to receive(:post_form).
with(uri, payload: '{"text":"OMG THIS IS NORMAL!!1"}')
job.perform('this is normal')
end
end
end

The first thing you’ll notice is that RSpec is very unhappy that it can’t find a SlackAnnoyerJob. Let’s make one, but then leave it empty:

# app/jobs/slack_annoyer_job.rb
class SlackAnnoyerJob < ApplicationJob
end

Next up: some wordy failure relating to Rails.application.config. It turns out RSpec won’t let us stub a method (slack_webhook_url) without defining it first. On Ministry of Velocity projects, we store all magic configuration variables in the environment, and our first stop along the way is an initializer:

# config/initializers/slack.rb
Rails.applicaton.config.slack_webhook_url = ENV.fetch('SLACK_WEBHOOK_URL')

Let’s make an entry in our .env file for the configuration that contains an Incoming Webhook URL for Slack:

# .env
SLACK_WEBHOOK_URL=SLACK_WEBHOOK_URL=https://hooks.slack.com/services/<webhook stuffs>

We now have a NotImplementedError relating to perform. Let’s touch up our Job first:

# app/jobs/slack_annoyer_job.rb
class SlackAnnoyerJob < ApplicationJob
def perform(text)
end
end

Finally, we get a real RSpec failure: Net::HTTP didn’t get called! Let’s fix that:

# app/jobs/slack_annoyer_job.rb
class SlackAnnoyerJob < ApplicationJob
def perform(text)
uri = URI.parse(Rails.application.config.slack_webhook_url)
payload = {text: "omg #{text}!!1".upcase}.to_json
Net::HTTP.post_form(uri, payload: payload)
end
end

This is great, and you’re guaranteed at least 5 minutes of fun before the novelty wears off!

Bonus round: fully integration test your jobs

Let’s revisit that original feature spec we wrote. Some of the time, we might want to just check that jobs are enqueued, but the rest of the time, we care about the full behavior and side effects caused by running the job immediately. We’ll use an RSpec spy around Net::HTTP for this purpose.

# spec/features/annoying_your_team_spec.rb
require 'rails_helper'
RSpec.describe 'Annoying your team', type: :feature do
before do
allow(Rails.application.config).to receive(:slack_webhook_url) {
'http://example.com/webhook'
}
allow(Net::HTTP).to receive(:post_form)
end
let(:uri) { URI.parse('http://example.com/webhook') }
scenario 'posts dramatic messages to slack' do
fill_in 'Message', with: 'can you just'
perform_enqueued_jobs do
click_on 'Send'
end
expect(Net::HTTP).to have_received(:post_form).
with(uri, '{"text":"OMG CAN YOU JUST!!1"}')
end
end

Ministry of Velocity is a full-service product development shop in San Francisco, California, currently occupying Mark Twain’s old office. These particular tips were provided by:

git pair hm cj dr

--

--