Skip to content

Commit

Permalink
Improve specs for Ears class (#37)
Browse files Browse the repository at this point in the history
* Extend tests for Ears

* Tidy.up

* Improve local tests

* Simplify Ears configuration initalize

* finalize

---------

Co-authored-by: Mike Blumtritt <mike.blumtritt@pm.me>
  • Loading branch information
johannesluedke and mblumtritt authored Oct 26, 2023
1 parent 177fdcd commit c110b62
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 166 deletions.
15 changes: 12 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@

require 'rake/clean'
require 'bundler/gem_tasks'
require 'rubocop'
require 'rubocop/rake_task'
require 'rspec/core/rake_task'
require 'yard'

CLEAN << '.yardoc'
CLOBBER << 'doc'
CLOBBER << 'coverage'
CLOBBER << 'doc' << 'coverage'

RSpec::Core::RakeTask.new(:spec)
YARD::Rake::YardocTask.new { |t| t.stats_options = %w[--list-undoc] }

task default: :spec
RuboCop::RakeTask.new(:rubocop) do |task|
task.formatters = ['simple']
task.fail_on_error = true
end

desc 'Run Prettier'
task(:prettier) { system('npm run lint') }

task default: %i[spec rubocop prettier]
2 changes: 1 addition & 1 deletion lib/ears.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
module Ears
class << self
# The global configuration for Ears.
#
# @attribute [r] configuration
# @return [Ears::Configuration]
def configuration
@configuration ||= Ears::Configuration.new
Expand Down
243 changes: 95 additions & 148 deletions spec/ears_spec.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
require 'ears'
# frozen_string_literal: true

RSpec.describe Ears do
let(:bunny) { instance_double(Bunny::Session) }
let(:channel) { instance_double(Bunny::Channel) }

before do
Ears.reset!
allow(Bunny).to receive(:new).and_return(bunny)
allow(bunny).to receive(:start)
allow(bunny).to receive(:create_channel).and_return(channel)
allow(channel).to receive(:prefetch).with(1)
allow(channel).to receive(:on_uncaught_exception)
end
before { Ears.reset! }

it 'has a version number' do
expect(Ears::VERSION).not_to be_nil
expect(Ears::VERSION).to match(/\A[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+/)
end

it 'has a configuration' do
Expand All @@ -24,200 +14,157 @@
describe '.configure' do
it 'allows setting the configuration values' do
Ears.configure do |config|
config.rabbitmq_url = 'test'
config.connection_name = 'conn'
config.rabbitmq_url = 'amqp://guest:guest@test.local:5672'
config.connection_name = 'conn_name'
config.recover_from_connection_close = true
config.recovery_attempts = 666
end

expect(Ears.configuration.rabbitmq_url).to eq('test')
expect(Ears.configuration).to have_attributes(
rabbitmq_url: 'amqp://guest:guest@test.local:5672',
connection_name: 'conn_name',
recover_from_connection_close: true,
recovery_attempts: 666,
)
end

it 'throws an error if connection name was not set' do
Ears.reset!
it 'yields the Ears configuration' do
Ears.configure do |config|
config.connection_name = 'conn_name'
expect(config).to be Ears.configuration
end
end

expect {
Ears.configure { |config| config.rabbitmq_url = 'test' }
}.to raise_error(Ears::Configuration::ConnectionNameMissing)
it 'throws an error if connection name was not set' do
expect do
Ears.configure { :empty_block }
end.to raise_error Ears::Configuration::ConnectionNameMissing
end
end

describe '.connection' do
let(:rabbitmq_url) { 'amqp://lol:lol@kek.com:15672' }
let(:connection_name) { 'my connection' }
let(:bunny_session) { instance_double(Bunny::Session, start: nil) }

context 'when only mandatory options are set' do
context 'with mandatory configuration' do
before do
Ears.configure do |config|
config.rabbitmq_url = rabbitmq_url
config.connection_name = connection_name
end
end

it 'connects with default config parameters when it is accessed' do
allow(Bunny).to receive(:new).and_return(bunny_session)
Ears.configure { |config| config.connection_name = 'conn_name' }
Ears.connection
end

it 'connects with config parameters' do
expect(Bunny).to have_received(:new).with(
rabbitmq_url,
connection_name: connection_name,
'amqp://guest:guest@localhost:5672',
connection_name: 'conn_name',
recovery_attempts: 10,
recovery_attempts_exhausted: anything,
) do |_args, kwargs|
proc = kwargs[:recovery_attempts_exhausted]
expect { proc.call }.to raise_error(
Ears::MaxRecoveryAttemptsExhaustedError,
)
end
expect(bunny).to have_received(:start)
)
end
end

context 'with more options' do
let(:recover_from_connection_close) { false }
let(:recovery_attempts) { nil }
it 'starts the connection' do
expect(bunny_session).to have_received(:start)
end
end

context 'with custom configration' do
before do
allow(Bunny).to receive(:new).and_return(bunny_session)
Ears.configure do |config|
config.rabbitmq_url = rabbitmq_url
config.connection_name = connection_name
config.recover_from_connection_close = recover_from_connection_close
config.recovery_attempts = recovery_attempts
config.rabbitmq_url = 'amqp://user:password@rabbitmq:15672'
config.connection_name = 'conn_name'
config.recover_from_connection_close = false
config.recovery_attempts = 9
end
end

it 'connects with config parameters when it is accessed' do
Ears.connection
end

it 'connects with config parameters' do
expect(Bunny).to have_received(:new).with(
rabbitmq_url,
connection_name: connection_name,
recover_from_connection_close: recover_from_connection_close,
'amqp://user:password@rabbitmq:15672',
connection_name: 'conn_name',
recover_from_connection_close: false,
recovery_attempts: 9,
recovery_attempts_exhausted: anything,
)
expect(bunny).to have_received(:start)
end

it 'starts the connection' do
expect(bunny_session).to have_received(:start)
end
end
end

describe '.channel' do
it 'creates a channel when it is accessed' do
expect(bunny).to receive(:create_channel).with(nil, 1, true).and_return(
channel,
)
expect(channel).to receive(:prefetch).with(1)
expect(channel).to receive(:on_uncaught_exception)

Ears.channel
let(:bunny_session) do
instance_double(Bunny::Session, start: nil, create_channel: bunny_channel)
end

it 'stores the channel on the current thread' do
expect(Ears.channel).to eq(Thread.current[:ears_channel])
let(:bunny_channel) do
instance_double(Bunny::Channel, prefetch: nil, on_uncaught_exception: nil)
end
end

describe '.setup' do
let(:exchange) { instance_double(Bunny::Exchange) }
let(:queue) { instance_double(Bunny::Queue, name: 'queue', options: {}) }
let(:consumer_wrapper) { instance_double(Ears::ConsumerWrapper) }
let(:delivery_info) { instance_double(Bunny::DeliveryInfo) }
let(:metadata) { instance_double(Bunny::MessageProperties) }
let(:payload) { 'my payload' }

before do
allow(Bunny::Exchange).to receive(:new).and_return(exchange)
allow(Bunny::Queue).to receive(:new).and_return(queue)
allow(queue).to receive(:bind)
allow(queue).to receive(:subscribe_with)
allow(queue).to receive(:channel).and_return(channel)
allow(Ears::ConsumerWrapper).to receive(:new).and_return(consumer_wrapper)
allow(consumer_wrapper).to receive(:on_delivery).and_yield(
delivery_info,
metadata,
payload,
)
allow(Thread).to receive(:new).and_yield

stub_const('MyConsumer', Class.new(Ears::Consumer))
allow(Bunny).to receive(:new).and_return(bunny_session)
Ears.channel
end

it 'creates a given exchange' do
expect(Bunny::Exchange).to receive(:new).with(
channel,
:topic,
'my-exchange',
{},
)

Ears.setup { exchange('my-exchange', :topic) }
it 'creates a channel when it is accessed' do
expect(bunny_session).to have_received(:create_channel).with(nil, 1, true)
end

it 'creates a queue' do
expect(Bunny::Queue).to receive(:new).with(channel, 'my-queue', {})

Ears.setup do
exchange('my-exchange', :topic)
queue('my-queue')
end
it 'configures the channel prefetch' do
expect(bunny_channel).to have_received(:prefetch).with(1)
end

it 'binds a queue to an exchange' do
expect(queue).to receive(:bind).with(exchange, routing_key: 'test')
it 'configures the channel exception handler' do
expect(bunny_channel).to have_received(:on_uncaught_exception)
end

Ears.setup do
exchange = exchange('my-exchange', :topic)
queue = queue('my-queue')
queue.bind(exchange, routing_key: 'test')
end
it 'stores the channel on the current thread' do
expect(Ears.channel).to eq(Thread.current[:ears_channel])
end
end

it 'starts a consumer subscribed to a queue' do
expect(consumer_wrapper).to receive(:on_delivery).and_yield(
delivery_info,
metadata,
payload,
).ordered
expect(consumer_wrapper).to receive(:process_delivery).with(
delivery_info,
metadata,
payload,
).ordered
expect(queue).to receive(:subscribe_with).with(consumer_wrapper).ordered

Ears.setup do
exchange = exchange('my-exchange', :topic)
queue = queue('my-queue')
queue.bind(exchange, routing_key: 'test')
consumer(queue, MyConsumer)
end
describe '.setup' do
it 'creates a setup helper and executed the given block on this instance' do
instance = :none
Ears.setup { instance = self }
expect(instance).to be_a Ears::Setup
end
end

describe '.stop!' do
before { allow(bunny).to receive(:close) }

it 'stops the connection' do
Ears.stop!

expect(bunny).to have_received(:close)
let(:bunny_session) do
instance_double(
Bunny::Session,
start: nil,
create_channel: bunny_channel,
close: nil,
)
end

it 'resets the connection' do
Ears.connection
let(:bunny_channel) do
instance_double(Bunny::Channel, prefetch: nil, on_uncaught_exception: nil)
end

before do
allow(Bunny).to receive(:new).and_return(bunny_session)
Ears.channel
Ears.stop!
end

Ears.connection
Ears.connection
it 'stops the connection' do
expect(bunny_session).to have_received(:close)
end

it 'forces to create a new connection afterwards' do
Ears.connection
expect(Bunny).to have_received(:new).twice
end

it 'resets the channel' do
Ears.channel

Ears.stop!

it 'forces to create a new channel afterwards' do
Ears.channel
Ears.channel

expect(bunny).to have_received(:create_channel).twice
expect(bunny_session).to have_received(:create_channel).twice
end
end
end
22 changes: 8 additions & 14 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
require 'bundler/setup'
# frozen_string_literal: true

require 'rspec/core'
require 'simplecov'
require 'ears'

SimpleCov.start do
enable_coverage :branch
primary_coverage :branch
end
if RSpec.configuration.files_to_run.length > 1
# Let's increase this later on
SimpleCov.minimum_coverage line: 97.5, branch: 65.7
end

RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
config.example_status_persistence_file_path = '.rspec_status'
RSpec.configure(&:disable_monkey_patching!)

# Disable RSpec exposing methods globally on `Module` and `main`
config.disable_monkey_patching!

config.expect_with :rspec do |c|
c.syntax = :expect
end
if RSpec.configuration.files_to_run.length > 1
# Let's increase this later on
SimpleCov.minimum_coverage line: 99.4, branch: 100
end

0 comments on commit c110b62

Please sign in to comment.