Reputation: 909
We have a Lockable
concern that allows for locks via Redis
module Lockable
extend ActiveSupport::Concern
def redis_lock(key, options = {})
Redis::Lock.new(
key,
expiration: options[:expiration] || 15,
timeout: options[:timeout] || 0.1
).lock { yield if block_given? }
end
end
We use this in a Controller method to ensure concurrent requests are handled correctly.
def create
redis_lock(<generated_key>, timeout: 15) do
# perform_operation
end
render json: <data>, status: :ok
end
When testing this action, I want to test that the correct generated_key
is being sent to Redis to initiate a lock.
I set up an expect for the Redis::Lock but that returns false always presumably because the request to create is sent mid request and not at the end of it.
expect(Redis::Lock).to receive(:create).once
Test structure:
context 'return status ok' do
When do
post :create, params: {
<params>
}
end
Then {
expect(Redis::Lock).to receive(:create).once
response.ok?
}
end
end
Since the lock is cleared at the end of the method call, I cannot check for the key in redis as a test.
This answer recommends setting up a fake class that matches the structure of Lockable to emulate the same behaviour but how do I write a test for it? The method we have does not return any value to verify.
Upvotes: 2
Views: 2263
Reputation: 2171
This is modified version of davegson using Rspec spies, This eliminates the coding smells like any_instances_of
describe 'Lockable' do
describe '#redis_lock' do
it "delegates functionality to Redis::Lock with proper arguments" do
# create an instance spy
redis_lock = instance_spy("Redis::Lock")
expect(Redis::Lock).to receive(:new).with('test', any_args).and_return(redis_lock)
redis_lock('test', timeout: 15) do
sleep 1
end
expect(redis_lock).to have_received(:lock)
end
end
end
Upvotes: 1
Reputation: 8331
From the code you provided I believe you just set up the wrong test:
expect(Redis::Lock).to receive(:create).once
This expects the Redis::Lock
class to receive a create
call, but you are calling create
in your controller.
What you are doing in the redis_lock
method is initializing an instance of Redis::Lock
and calling lock
on it. In my opinion, that is what you should test:
expect_any_instance_of(Redis::Lock).to receive(:lock).once
The Implementation would look something like this:
describe 'Lockable' do
describe '#redis_lock' do
subject { lockable.redis_lock(key, options) }
# you gotta set this
let(:lockable) { xyz }
let(:key) { xyz }
let(:options) { x: 'x', y: 'y' }
it 'calls Redis::Lock.new with correct arguments' do
expect(Redis::Lock).to receive(:new).with(key: key, options: options)
subject
end
it 'calls #lock on the created Redis::Lock instance' do
expect_any_instance_of(Redis::Lock).to receive(:lock).once
subject
end
end
end
Upvotes: 1