From 4d75f58991ca6b393ded2398de87fe6a13d4ac72 Mon Sep 17 00:00:00 2001
From: Edouard CHIN <edouard.chin@shopify.com>
Date: Wed, 26 Sep 2018 18:45:52 -0400
Subject: Add a way to check for subset of arguments when performing jobs:

- When calling `assert_performed_with`/`assert_enqueued_with`, the
  +args+ needs to match exactly what the job get passed.

  Some jobs can have lot of arguments, or even a simple hash argument
  has many key. This is not convenient to test as most tests doesn't
  need to check if the arguments matches perfectly.

  This PR make it possible to only check if a subset of arguments were
  passed to the job.
---
 activejob/CHANGELOG.md                   |  6 ++++
 activejob/lib/active_job/test_helper.rb  | 50 ++++++++++++++++++++++++++++++--
 activejob/test/cases/test_helper_test.rb | 46 +++++++++++++++++++++++++++++
 3 files changed, 100 insertions(+), 2 deletions(-)

(limited to 'activejob')

diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 57a62e963d..af5c197bac 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,9 @@
+*   Allow `assert_enqueued_with`/`assert_performed_with` methods to accept
+    a proc for the `args` argument. This is useful to check if only a subset of arguments
+    matches your expectations.
+
+    *Edouard Chin*
+
 *   `ActionDispatch::IntegrationTest` includes `ActiveJob::TestHelper` module by default.
 
     *Ricardo Díaz*
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 9efc8c0c12..e7a9b16277 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -331,6 +331,22 @@ module ActiveJob
     #     assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon)
     #   end
     #
+    #
+    # The +args+ argument also accepts a proc which will get passed the actual
+    # job's arguments. Your proc needs to returns a boolean value determining if
+    # the job's arguments matches your expectation. This is useful to check only
+    # for a subset of arguments.
+    #
+    #   def test_assert_enqueued_with
+    #     expected_args = ->(job_args) do
+    #       assert job_args.first.key?(:foo)
+    #     end
+    #
+    #     MyJob.perform_later(foo: 'bar', other_arg: 'No need to check in the test')
+    #     assert_enqueued_with(job: MyJob, args: expected_args, queue: 'low')
+    #   end
+    #
+    #
     # If a block is passed, that block should cause the job to be
     # enqueued with the given arguments.
     #
@@ -359,7 +375,14 @@ module ActiveJob
 
       matching_job = jobs.find do |enqueued_job|
         deserialized_job = deserialize_args_for_assertion(enqueued_job)
-        expected_args.all? { |key, value| value == deserialized_job[key] }
+
+        expected_args.all? do |key, value|
+          if value.respond_to?(:call)
+            value.call(deserialized_job[key])
+          else
+            value == deserialized_job[key]
+          end
+        end
       end
 
       assert matching_job, "No enqueued job found with #{expected}"
@@ -382,6 +405,22 @@ module ActiveJob
     #     assert_performed_with(job: MyJob, at: Date.tomorrow.noon)
     #   end
     #
+    # The +args+ argument also accepts a proc which will get passed the actual
+    # job's arguments. Your proc needs to returns a boolean value determining if
+    # the job's arguments matches your expectation. This is useful to check only
+    # for a subset of arguments.
+    #
+    #   def test_assert_performed_with
+    #     expected_args = ->(job_args) do
+    #       assert job_args.first.key?(:foo)
+    #     end
+    #     MyJob.perform_later(foo: 'bar', other_arg: 'No need to check in the test')
+    #
+    #     perform_enqueued_jobs
+    #
+    #     assert_performed_with(job: MyJob, args: expected_args, queue: 'high')
+    #   end
+    #
     # If a block is passed, that block performs all of the jobs that were
     # enqueued throughout the duration of the block and asserts that
     # the job has been performed with the given arguments in the block.
@@ -411,7 +450,14 @@ module ActiveJob
 
       matching_job = jobs.find do |enqueued_job|
         deserialized_job = deserialize_args_for_assertion(enqueued_job)
-        expected_args.all? { |key, value| value == deserialized_job[key] }
+
+        expected_args.all? do |key, value|
+          if value.respond_to?(:call)
+            value.call(deserialized_job[key])
+          else
+            value == deserialized_job[key]
+          end
+        end
       end
 
       assert matching_job, "No performed job found with #{expected}"
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 018c40c28f..83c71ab1c4 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -538,6 +538,29 @@ class EnqueuedJobsTest < ActiveJob::TestCase
     end
   end
 
+  def test_assert_enqueued_with_selective_args
+    args = ->(job_args) do
+      assert_equal 1, job_args.first[:argument1]
+      assert job_args.first[:argument2].key?(:b)
+    end
+
+    assert_enqueued_with(job: MultipleKwargsJob, args: args) do
+      MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
+    end
+  end
+
+  def test_assert_enqueued_with_selective_args_fails
+    args = ->(job_args) do
+      false
+    end
+
+    assert_raise ActiveSupport::TestCase::Assertion do
+      assert_enqueued_with(job: MultipleKwargsJob, args: args) do
+        MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
+      end
+    end
+  end
+
   def test_assert_enqueued_with_with_no_block_args
     assert_raise ArgumentError do
       NestedJob.set(wait_until: Date.tomorrow.noon).perform_later
@@ -1579,6 +1602,29 @@ class PerformedJobsTest < ActiveJob::TestCase
     end
   end
 
+  def test_assert_performed_with_selective_args
+    args = ->(job_args) do
+      assert_equal 1, job_args.first[:argument1]
+      assert job_args.first[:argument2].key?(:b)
+    end
+
+    assert_performed_with(job: MultipleKwargsJob, args: args) do
+      MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
+    end
+  end
+
+  def test_assert_performed_with_selective_args_fails
+    args = ->(job_args) do
+      false
+    end
+
+    assert_raise ActiveSupport::TestCase::Assertion do
+      assert_performed_with(job: MultipleKwargsJob, args: args) do
+        MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
+      end
+    end
+  end
+
   def test_assert_performed_with_with_global_id_args
     ricardo = Person.new(9)
     assert_performed_with(job: HelloJob, args: [ricardo]) do
-- 
cgit v1.2.3