Skip to content

Instantly share code, notes, and snippets.

@arturictus
Created October 8, 2015 09:03
Show Gist options
  • Select an option

  • Save arturictus/bbdf020b2deca2e29c94 to your computer and use it in GitHub Desktop.

Select an option

Save arturictus/bbdf020b2deca2e29c94 to your computer and use it in GitHub Desktop.

Revisions

  1. arturictus created this gist Oct 8, 2015.
    356 changes: 356 additions & 0 deletions rspec_helper.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,356 @@
    def specific_enqueued_jobs(job)
    enqueued_jobs.select{ |j| j if j[:job] == job }
    end
    require 'active_support/core_ext/class/subclasses'
    require 'active_support/core_ext/hash/keys'

    module ActiveJob
    # Provides helper methods for testing Active Job
    module RSpecHelper
    extend ActiveSupport::Concern

    included do
    def before_setup # :nodoc:
    test_adapter = ActiveJob::QueueAdapters::TestAdapter.new

    @old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass|
    # only override explicitly set adapters, a quirk of `class_attribute`
    klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
    end.map do |klass|
    [klass, klass.queue_adapter].tap do
    klass.queue_adapter = test_adapter
    end
    end

    clear_enqueued_jobs
    clear_performed_jobs
    super
    end

    def after_teardown # :nodoc:
    super
    @old_queue_adapters.each do |(klass, adapter)|
    klass.queue_adapter = adapter
    end
    end

    # Asserts that the number of enqueued jobs matches the given number.
    #
    # def test_jobs
    # expect_enqueued_jobs 0
    # HelloJob.perform_later('david')
    # expect_enqueued_jobs 1
    # HelloJob.perform_later('abdelkader')
    # expect_enqueued_jobs 2
    # end
    #
    # If a block is passed, that block should cause the specified number of
    # jobs to be enqueued.
    #
    # def test_jobs_again
    # expect_enqueued_jobs 1 do
    # HelloJob.perform_later('cristian')
    # end
    #
    # expect_enqueued_jobs 2 do
    # HelloJob.perform_later('aaron')
    # HelloJob.perform_later('rafael')
    # end
    # end
    #
    # The number of times a specific job is enqueued can be asserted.
    #
    # def test_logging_job
    # expect_enqueued_jobs 2, only: LoggingJob do
    # LoggingJob.perform_later
    # HelloJob.perform_later('jeremy')
    # end
    # end
    require 'rspec/expectations'

    RSpec::Matchers.define :have_enqueued_jobs do |expected|
    match do |actual|
    actual == expected
    end
    failure_message do |actual|
    "#{expected} jobs expected, but #{actual} were enqueued"
    end
    end
    def expect_enqueued_jobs(number, only: nil)
    if block_given?
    original_count = enqueued_jobs_size(only: only)
    yield
    new_count = enqueued_jobs_size(only: only)
    expect(number).to have_enqueued_jobs(new_count - original_count)
    else
    actual_count = enqueued_jobs_size(only: only)
    expect(number).to have_enqueued_jobs(actual_count)
    end
    end

    # Asserts that no jobs have been enqueued.
    #
    # def test_jobs
    # expect_no_enqueued_jobs
    # HelloJob.perform_later('jeremy')
    # expect_enqueued_jobs 1
    # end
    #
    # If a block is passed, that block should not cause any job to be enqueued.
    #
    # def test_jobs_again
    # expect_no_enqueued_jobs do
    # # No job should be enqueued from this block
    # end
    # end
    #
    # It can be asserted that no jobs of a specific kind are enqueued:
    #
    # def test_no_logging
    # expect_no_enqueued_jobs only: LoggingJob do
    # HelloJob.perform_later('jeremy')
    # end
    # end
    #
    # Note: This assertion is simply a shortcut for:
    #
    # expect_enqueued_jobs 0, &block
    def expect_no_enqueued_jobs(only: nil, &block)
    expect_enqueued_jobs 0, only: only, &block
    end

    # Asserts that the number of performed jobs matches the given number.
    # If no block is passed, <tt>perform_enqueued_jobs</tt>
    # must be called around the job call.
    #
    # def test_jobs
    # expect_performed_jobs 0
    #
    # perform_enqueued_jobs do
    # HelloJob.perform_later('xavier')
    # end
    # expect_performed_jobs 1
    #
    # perform_enqueued_jobs do
    # HelloJob.perform_later('yves')
    # expect_performed_jobs 2
    # end
    # end
    #
    # If a block is passed, that block should cause the specified number of
    # jobs to be performed.
    #
    # def test_jobs_again
    # expect_performed_jobs 1 do
    # HelloJob.perform_later('robin')
    # end
    #
    # expect_performed_jobs 2 do
    # HelloJob.perform_later('carlos')
    # HelloJob.perform_later('sean')
    # end
    # end
    #
    # The block form supports filtering. If the :only option is specified,
    # then only the listed job(s) will be performed.
    #
    # def test_hello_job
    # expect_performed_jobs 1, only: HelloJob do
    # HelloJob.perform_later('jeremy')
    # LoggingJob.perform_later
    # end
    # end
    #
    # An array may also be specified, to support testing multiple jobs.
    #
    # def test_hello_and_logging_jobs
    # assert_nothing_raised do
    # expect_performed_jobs 2, only: [HelloJob, LoggingJob] do
    # HelloJob.perform_later('jeremy')
    # LoggingJob.perform_later('stewie')
    # RescueJob.perform_later('david')
    # end
    # end
    # end
    RSpec::Matchers.define :have_performed_jobs do |expected|
    match do |actual|
    actual == expected
    end
    failure_message do |actual|
    "#{expected} jobs expected, but #{actual} were performed"
    end
    end
    def expect_performed_jobs(number, only: nil)
    if block_given?
    original_count = performed_jobs.size
    perform_enqueued_jobs(only: only) { yield }
    new_count = performed_jobs.size
    expect(number).to have_performed_jobs(new_count - original_count)
    else
    performed_jobs_size = performed_jobs.size
    expect(number).to have_performed_jobs(performed_jobs_size)
    end
    end

    # Asserts that no jobs have been performed.
    #
    # def test_jobs
    # expect_no_performed_jobs
    #
    # perform_enqueued_jobs do
    # HelloJob.perform_later('matthew')
    # expect_performed_jobs 1
    # end
    # end
    #
    # If a block is passed, that block should not cause any job to be performed.
    #
    # def test_jobs_again
    # expect_no_performed_jobs do
    # # No job should be performed from this block
    # end
    # end
    #
    # The block form supports filtering. If the :only option is specified,
    # then only the listed job(s) will be performed.
    #
    # def test_hello_job
    # expect_performed_jobs 1, only: HelloJob do
    # HelloJob.perform_later('jeremy')
    # LoggingJob.perform_later
    # end
    # end
    #
    # An array may also be specified, to support testing multiple jobs.
    #
    # def test_hello_and_logging_jobs
    # assert_nothing_raised do
    # expect_performed_jobs 2, only: [HelloJob, LoggingJob] do
    # HelloJob.perform_later('jeremy')
    # LoggingJob.perform_later('stewie')
    # RescueJob.perform_later('david')
    # end
    # end
    # end
    #
    # Note: This assertion is simply a shortcut for:
    #
    # expect_performed_jobs 0, &block
    def expect_no_performed_jobs(only: nil, &block)
    expect_performed_jobs 0, only: only, &block
    end

    # Asserts that the job passed in the block has been enqueued with the given arguments.
    #
    # def test_assert_enqueued_with
    # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
    # MyJob.perform_later(1,2,3)
    # end
    #
    # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do
    # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
    # end
    # end
    RSpec::Matchers.define :have_enqueued_job do |expected|
    match do |actual|
    actual == expected
    end
    failure_message do |actual|
    "#{expected} jobs expected, but #{actual} were performed"
    end
    end
    def assert_enqueued_with(args = {})
    original_enqueued_jobs_count = enqueued_jobs.count
    args.assert_valid_keys(:job, :args, :at, :queue)
    serialized_args = serialize_args_for_assertion(args)
    yield
    in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
    matching_job = in_block_jobs.find do |job|
    serialized_args.all? { |key, value| value == job[key] }
    end
    assert matching_job, "No enqueued job found with #{args}"
    instantiate_job(matching_job)
    end

    # Asserts that the job passed in the block has been performed with the given arguments.
    #
    # def test_assert_performed_with
    # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
    # MyJob.perform_later(1,2,3)
    # end
    #
    # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do
    # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
    # end
    # end
    def assert_performed_with(args = {})
    original_performed_jobs_count = performed_jobs.count
    args.assert_valid_keys(:job, :args, :at, :queue)
    serialized_args = serialize_args_for_assertion(args)
    perform_enqueued_jobs { yield }
    in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
    matching_job = in_block_jobs.find do |job|
    serialized_args.all? { |key, value| value == job[key] }
    end
    assert matching_job, "No performed job found with #{args}"
    instantiate_job(matching_job)
    end

    def perform_enqueued_jobs(only: nil)
    old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
    old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
    old_filter = queue_adapter.filter

    begin
    queue_adapter.perform_enqueued_jobs = true
    queue_adapter.perform_enqueued_at_jobs = true
    queue_adapter.filter = only
    yield
    ensure
    queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
    queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
    queue_adapter.filter = old_filter
    end
    end

    def queue_adapter
    ActiveJob::Base.queue_adapter
    end

    delegate :enqueued_jobs, :enqueued_jobs=,
    :performed_jobs, :performed_jobs=,
    to: :queue_adapter

    private
    def clear_enqueued_jobs # :nodoc:
    enqueued_jobs.clear
    end

    def clear_performed_jobs # :nodoc:
    performed_jobs.clear
    end

    def enqueued_jobs_size(only: nil) # :nodoc:
    if only
    enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) }
    else
    enqueued_jobs.count
    end
    end

    def serialize_args_for_assertion(args) # :nodoc:
    args.dup.tap do |serialized_args|
    serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args]
    serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at]
    end
    end

    def instantiate_job(payload) # :nodoc:
    job = payload[:job].new(*payload[:args])
    job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
    job.queue_name = payload[:queue]
    job
    end
    end
    end
    end