ロック機構をテストする方法
概要
BankAccountTransaction を BankAccount にインポートするコードがあります
bank_account.with_lock do
transactions.each do |transaction|
import(bank_account, transaction)
end
end
正常に動作しますが、トランザクションを 2 回インポートしていないことを 100% 確信できるように、RSpec ケースを書き込む必要があります。
次のヘルパーを書きました
module ConcurrencyHelper
def make_concurrent_calls(function, concurrent_calls: 2)
threads = Array.new(concurrent_calls) do
thread = Thread.new { function.call }
thread.abort_on_exception = true
thread
end
threads.each(&:join)
end
end
そして私はそれをRSpecで呼び出しています
context 'when importing the same transaction twice' do
subject(:concurrent_calls) { make_concurrent_calls(operation) }
let!(:operation) { -> { described_class.call(params) } }
let(:filename) { 'single-transaction-response.xml' }
it 'creates only one transaction' do
expect { concurrent_calls }.to change(BankaccountTransaction, :count).by(1)
end
end
しかし何も起こりません。テスト スーツはこの時点でスタックし、エラーなどはスローされません。
スレッドをインスタンス化した直後にデバッグ ポイント (byebug) を配置し、関数を呼び出そうとしましたが、正常に実行されますが、スレッドに参加しても何も起こりません。
これまでに試したこと
他に何かアイデアはありますか?
編集
これが私の現在の DatabaseCleaner 構成です
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.clean_with(:deletion)
end
config.before do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :deletion
end
config.before do
DatabaseCleaner.start
end
config.after do
DatabaseCleaner.clean
end
end
まだ戦略を :deleteion に変更しようとしていません。同様に変更してみます
解決策
with_lock は Rails の実装であり、テストする必要はありません。モックを使用して、コードが with_lock を呼び出すかどうかを確認できます。ここでの唯一のトリックは、トランザクションが確実にインポートされるようにすることです (つまり、with_lock 内のコードが実行されます)。 RSpec は、呼び出すことができるブロックを提供します。以下はその方法の抜粋です。完全に動作する実装はここにあります。
describe "#import_transactions" do
it "runs with lock" do
# Test if with_lock is getting called
expect(subject).to receive(:with_lock) do |*_args, &block|
# block is provided to with_lock method
# execute the block and test if it creates transactions
expect { block.call }
.to change { BankAccountTransaction.count }.from(0).to(2)
end
ImportService.new.import_transactions(subject, transactions)
end
end