Rails + RSpecで、引数が必要なRake Taskのテストコードを書いてみた

Rails + RSpecにて、Rake Taskのテストコードを書く機会がありました。

ただ、引数が必要なRake Taskのテストを書くときに悩んだことがあったため、メモを残します。

 
目次

 

環境

 
また、 rake_helper.rb に以下の設定が記載されているものとします。

require 'rails_helper'
require 'rake'

RSpec.configure do |config|
  config.before(:suite) do
    Rails.application.load_tasks
  end

  config.before(:each) do
    Rake.application.tasks.each(&:reenable)
  end
end

 

引数なしのRake Taskをテストする

以下のようなRake Taskがあるとします。

namespace :rake_option do
  desc 'オプションなしのタスク'
  task without_option: :environment do
    Rails.logger.error('foo')
  end
end

 
開発環境でこのタスクを実行すると、ログファイルに foo が出力されます。

# 実行
$bin/rails rake_option:without_option

# 確認
$ tail -1 log/development.log 
foo

 
このタスクに対するRSpecのテストコードは以下です。

invoke メソッドを引数無しで実行することで、Rake Taskが実行されます。

require 'rake_helper'

RSpec.describe 'rake_option', type: :task do
  before do
    # loggerをモック
    allow(Rails.logger).to receive(:error)
  end

  describe 'without_option' do
    subject(:task) { Rake.application['rake_option:without_option'] }

    it 'ログに出力されること' do
      task.invoke

      expect(Rails.logger).to have_received(:error).with('foo')
    end
  end
end

 

引数ありのRake Taskをテストする

Rake Taskへ引数を渡すには、いくつか方法があるようです。
4 Ways to Pass Arguments to a Rake Task | Sean C Davis

 
今回は

  • [] を使って値を渡すパターン
  • ENV を介して値を渡すパターン

の2つをためします。

 

[] を使って値を渡すパターンのRake Taskをテストする

[] を使ってRake Taskに値を渡す場合、Rake Taskの定義は以下となります。

namespace :rake_option do
  desc '[]で囲った引数ありのタスク'
  task :with_option, [:foo, :bar] => :environment do |_task, args|
    Rails.logger.error("#{args[:foo]}, type => #{args[:foo].class}")
    Rails.logger.error("#{args[:bar]}, type => #{args[:bar].class}")
  end
end

 
開発環境でこのタスクを実行するには、[]で引数の値を囲います。

なお、カンマの前後に空白を付けてしまうと動作しなくなります。
Ruby on Railsの rake taskの挙動について

# 実行
$ bin/rails rake_option:with_option[1,true]

# 確認
$ tail -2 log/development.log 
1, type => String
true, type => String

 
このRake Taskに対するテストコードは以下となります。

invoke メソッドの引数に、[] 内の値を , で区切って渡しています。

RSpec.describe 'rake_option', type: :task do
  before do
    # loggerをモック
    allow(Rails.logger).to receive(:error)
  end

  describe 'with_option' do
    subject(:task) { Rake.application['rake_option:with_option'] }

    it 'ログに出力されること' do
      task.invoke('foo', 'true')

      expect(Rails.logger).to have_received(:error).with('foo, type => String')
      expect(Rails.logger).to have_received(:error).with('true, type => String')
    end
  end
end

ENV を介して値を渡すパターンのRake Taskをテストする

ENV を介してRake Taskに値を渡す場合、Rake Taskの定義は以下となります。

なお、今回は引数の指定を必須とするため、ENVから値を取得するときに fetch を使います。
ENV.fetch のすすめ - ~saiya/hatenablog

namespace :rake_option do
  desc 'ENVに入る引数のタスク'
  task with_env: :environment do
    foo = ENV.fetch('foo')
    bar = ENV.fetch('bar')

    Rails.logger.error("#{foo}, type => #{foo.class}")
    Rails.logger.error("#{bar}, type => #{bar.class}")
  end
end

 
開発環境でこのタスクを実行するには、引数をスペースで区切って key=value の形で指定します。

# 実行
$ bin/rails rake_option:with_env foo=3 bar=false

# 確認
$ tail -2 log/development.log 
3, type => String
false, type => String

 
このRake Taskに対してテストを書くときには、以下に注意します。

 
注意点を踏まえたテストコードは以下です。

RSpec.describe 'rake_option', type: :task do
  before do
    # loggerをモック
    allow(Rails.logger).to receive(:error)
  end

  describe 'with_env' do
    subject(:task) { Rake.application['rake_option:with_env'] }

    before do
      allow(ENV).to receive(:fetch).and_call_original # fooとbar以外は元の実装のままにする
      allow(ENV).to receive(:fetch).with('foo').and_return('1')
      allow(ENV).to receive(:fetch).with('bar').and_return('false')
    end

    it 'ログに出力されること' do
      task.invoke('foo=1', 'bar=false')

      expect(Rails.logger).to have_received(:error).with('1, type => String')
      expect(Rails.logger).to have_received(:error).with('false, type => String')
    end
  end
end

 
ちなみに、ENV をモックしているところをコメントアウトすると

  describe 'with_env' do
    subject(:task) { Rake.application['rake_option:with_env'] }

    before do
      allow(ENV).to receive(:fetch).and_call_original # fooとbar以外は元の実装のままにする
      allow(ENV).to receive(:fetch).with('foo').and_return('1')
      # allow(ENV).to receive(:fetch).with('bar').and_return('false')
    end
#...

以下のように KeyError となります。

KeyError: key not found: "bar"

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/rails_7_0_minimal_app

今回のプルリクはこちら
https://github.com/thinkAmi-sandbox/rails_7_0_minimal_app/pull/12