Allowing Unit Tests to Work as Intended
April 15th, 2012 // 11:55 am @ matt
Several months ago, I had the opportunity to improve part of a testing suite for an application I worked on. Specifically, I was improving some unit testing pertaining to the creation of DelayedJob entries when emailing users.
The code being tested related to users being emailed upon registration on the site.
def User < ActiveRecord::Base after_create :send_welcome_email def send_welcome_email UserMailer.welcome(email).deliver end handle_asynchronously :send_welcome_email end
The test’s original implementation compared the number of delayed jobs being created to the number of expected delayed job entries. The unit test code essentially looked like this.
class UserTest < Test::Unit::TestCase context "when creating a new user" do setup do @user = Factory.create(:user) end should "send new user a welcome email" do assert_equal 1, Delayed::Job.count end end end
Since the email is sent via a delayed job, its implementation for success is to compare the number of delayed jobs to the number of expected delayed jobs (in this case, just one). Everything works, and the test passes successfully. That’s great for this particular instance.
But what happens if we change the User model’s code to the following?
def User < ActiveRecord::Base after_create :send_notification_to_referring_user def send_notification_to_referring_user UserMailer.referral(referrer.email).deliver if referrer end handle_asynchronously :send_notification_to_referring_user end
When we run our unit test again, it still passes. However, the unit test is supposed to be ensuring the user receives a welcome email, given the line of code reading should "send new user a welcome email". Clearly given the updated model code, that is no longer happening.
An improvement for our test would be to check that the correct method is being called via Delayed Job.
assert_delayed_method :send_notificationsThere is no assert_delayed_method supplied by default, but that doesn’t mean we can’t create one. Within test/test_helper.rb, let’s go ahead and add the following code.
def assert_delayed_method(method_name, msg = nil) method_name = method_name.to_sym full_message = build_message(msg, "Delayed Job should exist for ?.", method_name) assert_block(full_message) do match = false Delayed::Job.find_each do |job| match = true and break if job.payload_object.method == method_name end match end end
Now, let’s change our unit test to take advantage of this new assertion method.
class UserTest < Test::Unit::TestCase context "when creating a new user" do setup do @user = Factory.create(:user) end should "send new user a welcome email" do assert_delayed_method :send_notification_to_referring_user end end end
The unit test still passes, and we can rest assured that if a different delayed job is created in the future, our test will fail.