Search code examples
ruby-on-railsrspecrake-taskrspec-mocks

Write rspec for rake task


i have task /lib/crawler.rake like that:

namespace :crawler do
  area_names = Dir[Rails.root.join("lib", "crawler", "*.rb")].map do |file_name|
    File.basename(file_name, ".rb")
  end

  area_names.each do |area_name|
    task area_name.to_sym => :environment do
      logger = Logger.new("log/crawl_#{area_name}.log")

      # do something

      parallel_results = crawler.crawl
      mutex = Mutex.new

      Parallel.each(parallel_results, in_threads: [parallel_results.count, CRAWL_CONFIG["building_thread_max"]].min) do |pages|
        begin
          # do something
        rescue => e
          # do something
          raise e
        end
      end

      Availability.update_by_grounds_and_time
    end
  end
end

Logic here, if everything's ok after parallel, we'll call update_by_grounds_and_time method to update Availability; if get error, we'll stop action and raise error.

So i want to write rspec to test for these cases, i want to mock/stub output of task here (pass or raise error) and check did we call update_by_grounds_and_time method? Can we don't need invoke really task? can we use Rspec Mock?

Can you help me! Thank


Solution

  • If it is defined in Rakefile, try this:

    require 'rake'
    
    RSpec.describe "Rake Tasks" do
      before do
        file, path = Rake.application.find_rakefile_location
        Rake.load_rakefile("#{path}/#{file}")
      end
    
      it "should invoke some tasks" do
        expect(Availability).to receive(:update_by_grounds_and_time)
        Rake.application["crawler:#{area_name}"].invoke
      end
    end
    

    If it is defined in foo.rake, then try this one:

    require 'rake'
    
    RSpec.describe "Rake Tasks" do
      before do
        Rake.application.rake_require('/path/to/lib/tasks/foo')
      end
    
      it "should invoke some tasks" do
        expect(Availability).to receive(:update_by_grounds_and_time)
        Rake.application["crawler:#{area_name}"].invoke
      end
    end
    

    UPDATE (error case)

    For example

    # foo.rake
    Parallel.each(parallel_results, in_threads: [parallel_results.count, CRAWL_CONFIG["building_thread_max"]].min) do |pages|
      begin
        foo = Foo.new
        foo.bar
        # do something else
      rescue => e
        # do something
        raise e
      end
    end
    
    # foo_spec.rb
    require 'rake'
    
    RSpec.describe "Rake Tasks" do
      before do
        Rake.application.rake_require('/path/to/lib/tasks/foo')
      end
    
      it "should not call Availability#update_by_grounds_and_time if error raised" do
        allow_any_instance_of(Foo).to receive(:bar).and_raise(StandardError)
        expect(Availability).to_not receive(:update_by_grounds_and_time)
        expect { Rake.application["crawler:#{area_name}"].invoke }.to raise_error(StandardError)
      end
    end