Madogiwa Blog

主に技術系の学習メモに使っていきます。

Rubyで特定の条件を満たすまで待機(Sleep)する

はじめに

非同期でAPIにPOSTを投げて、データベースにレコードが作成されてからテストを始めたいみたいなときに、単純にsleep 1みたいなことをやってしまうとテスト実行環境のパフォーマンスによって落ちてしまう可能性があり、あまり好ましくないですよね💧

そういうときに役に立ちそうな特定の条件がtrueを返すまでsleepするメソッドを作ったのでメモしておきます✍

実際のコード

下記が実際のコードです、wait_conditionにブロックを渡すのそのblockがtrueを返すまで処理を待機します。一応引数にintevalを渡すと渡したブロックを実行する間隔を指定できて、limitを渡すと待機する最大の秒数を定義します。
※limitに指定した秒数を超えても最低1回はブロックが実行されてしまうので、注意してください⚠

def wait_condition(interval: 0.5, limit: 10, &condition)
  start_at = Time.now
  raise "must give block!" unless block_given?
  while !condition.call do
    sleep interval
    break puts("time out") if (Time.now - start_at) > limit
  end
end

def two_sec_afert_true
  sleep 2
  true
end

wait_condition { two_sec_afert_true }
puts "fire!"

ちなみに上記のコードを実行すると約2秒後に"fire!"がが出力されます🔥

例: DBにレコードが作成されるまで待つ

内部のAPIの呼び出しを行い、実際にデータが作成されるまで待つようなケースでは下記のようなコードで待つことができます⏳
※Mock作ったほうがいいとかそういうケースもありますが、、、

RSpec.describe Book, type: :model do
  def wait_condition(interval: 0.5, limit: 10, &condition)
    start_at = Time.now
    raise "must give block!" unless block_given?
    while !condition.call do
      sleep interval
      break puts("time out") if (Time.now - start_at) > limit
    end
  end

  before do
    # Bookを作成するAPIにPOSTを投げる処理を実行
    wait_condition { !Book.count.zero? } # Bookが1件でも作成されるまで待機
  end

  it 'Bookが作成されること' do
    expect(Book.take.title).to eq 'book title'
  end
end

おわりに

sleep 1とかを使ってしまうとたまに落ちるテストになってしまう可能性があるので、なるべく特定の条件で待つようにするか、Mock等を用意するようにしたいですね👀

参考

docs.ruby-lang.org