如何通过 Rspec 测试 Redis 锁
How to test Redis Lock via Rspec
我们有一个 Lockable
问题,允许通过 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
我们在 Controller 方法中使用它来确保正确处理并发请求。
def create
redis_lock(<generated_key>, timeout: 15) do
# perform_operation
end
render json: <data>, status: :ok
end
测试此操作时,我想测试是否将正确的 generated_key
发送到 Redis 以启动锁定。
我为 Redis::Lock 设置了期望,但 return 总是错误的,大概是因为创建请求是在请求中间而不是在请求结束时发送的。
expect(Redis::Lock).to receive(:create).once
测试结构:
context 'return status ok' do
When do
post :create, params: {
<params>
}
end
Then {
expect(Redis::Lock).to receive(:create).once
response.ok?
}
end
end
由于锁在方法调用结束时被清除,我无法在redis中检查密钥作为测试。
建议设置一个与 Lockable 的结构相匹配的假 class 来模拟相同的行为,但我如何为它编写测试?我们的方法没有 return 任何要验证的值。
根据您提供的代码,我认为您只是设置了错误的测试:
expect(Redis::Lock).to receive(:create).once
这预计 Redis::Lock
class 会收到 create
调用,但您正在控制器中调用 create
。
你在redis_lock
方法中所做的是初始化Redis::Lock
的实例并调用lock
就可以了。在我看来,这就是您应该测试的内容:
expect_any_instance_of(Redis::Lock).to receive(:lock).once
实现看起来像这样:
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
这是 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
我们有一个 Lockable
问题,允许通过 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
我们在 Controller 方法中使用它来确保正确处理并发请求。
def create
redis_lock(<generated_key>, timeout: 15) do
# perform_operation
end
render json: <data>, status: :ok
end
测试此操作时,我想测试是否将正确的 generated_key
发送到 Redis 以启动锁定。
我为 Redis::Lock 设置了期望,但 return 总是错误的,大概是因为创建请求是在请求中间而不是在请求结束时发送的。
expect(Redis::Lock).to receive(:create).once
测试结构:
context 'return status ok' do
When do
post :create, params: {
<params>
}
end
Then {
expect(Redis::Lock).to receive(:create).once
response.ok?
}
end
end
由于锁在方法调用结束时被清除,我无法在redis中检查密钥作为测试。
根据您提供的代码,我认为您只是设置了错误的测试:
expect(Redis::Lock).to receive(:create).once
这预计 Redis::Lock
class 会收到 create
调用,但您正在控制器中调用 create
。
你在redis_lock
方法中所做的是初始化Redis::Lock
的实例并调用lock
就可以了。在我看来,这就是您应该测试的内容:
expect_any_instance_of(Redis::Lock).to receive(:lock).once
实现看起来像这样:
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
这是 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