Skip to content

Commit

Permalink
Add Omnes::Bus#performing_only and remove #with_subscriptions
Browse files Browse the repository at this point in the history
The new method `#performing_only` runs a given code block with only the
given subscriptions. Once the block is over, all previous subscriptions
are restored.

It plays well with `Omnes` as an including module, as the old
`#with_subscriptions` wasn't useful in that context (call to
`#initialize`, which is something we don't control).

```ruby
creation_subscription = bus.subscribe(:order_created, OrderCreationEmailSubscriber.new)
deletion_subscription = bus.subscribe(:order_deleted, OrderDeletionSubscriber.new)
bus.performing_only(creation_subscription) do
  bus.publish(:order_created, number: order.number, user_email: user.email) # `creation_subscription` will run
  bus.publish(:order_deleted, number: order.number) # `deletion_subscription` won't run
end
bus.publish(:order_deleted, number: order.number) # `deletion_subscription` will run
```
  • Loading branch information
waiting-for-dev committed Mar 23, 2022
1 parent 9ae6ace commit 0c8dc16
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 33 deletions.
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ When you create a subscription, an instance of
debug some behavior.

```ruby
subscription = bus.subscribe(:create_order, OrderCreationEmailSubscription.new)
subscription = bus.subscribe(:order_created, OrderCreationEmailSubscription.new)
bus.unsubscribe(subscription)
```

Expand Down Expand Up @@ -499,18 +499,29 @@ operation at the integration level. Example:

```ruby
if # test environment
bus.subscribe(OrderCreationEmailSubscriber.new(service: MockService.new)
bus.subscribe(:order_created, OrderCreationEmailSubscriber.new(service: MockService.new)
else
bus.subscribe(OrderCreationEmailSubscriber.new)
bus.subscribe(:order_created, OrderCreationEmailSubscriber.new)
end
```

Then, at the unit level, you can test your subscribers as any other class.

However, there's also a handy `Omnes::Bus#with_subscriptions` method that
returns a copy of the current bus but with only the given array of
`Omnes::Subscription` instances. You can use it to substitute a globally
available bus while you're testing a given component.
However, there's also a handy `Omnes::Bus#performing_only` method that allows
running a code block with only a selection of subscriptions as potential
callbacks for published events.

```ruby
creation_subscription = bus.subscribe(:order_created, OrderCreationEmailSubscriber.new)
deletion_subscription = bus.subscribe(:order_deleted, OrderDeletionSubscriber.new)
bus.performing_only(creation_subscription) do
bus.publish(:order_created, number: order.number, user_email: user.email) # `creation_subscription` will run
bus.publish(:order_deleted, number: order.number) # `deletion_subscription` won't run
end
bus.publish(:order_deleted, number: order.number) # `deletion_subscription` will run
```

Remember that the array of created subscriptions is returned on `Omnes::Subscriber#subscribe_to`.

## Recipes

Expand Down
27 changes: 20 additions & 7 deletions lib/omnes/bus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,30 @@ def unsubscribe(subscription)
@subscriptions.delete(subscription)
end

# Returns a new bus with same registry and only the specified subscriptions
# Runs given block performing only a selection of subscriptions
#
# That's something useful for testing purposes, as it allows to silence
# subscriptions that are not part of the system under test.
#
# @param subscriptions [Array<Omnes::Subscription>]
def with_subscriptions(subscriptions)
Bus.new(
subscriptions: subscriptions,
registry: registry
)
# After the block is over, original subscriptions are restored.
#
# @param selection [Array<Omnes::Subscription>]
# @yield Block to run
#
# @raise [Omnes::UnknownSubscriptionError] when the subscription is not
# known by the bus
def performing_only(*selection)
selection.each do |subscription|
unless subscriptions.include?(subscription)
raise UnknownSubscriptionError.new(subscription: subscription,
bus: self)
end
end
all_subscriptions = subscriptions
@subscriptions = selection
yield
ensure
@subscriptions = all_subscriptions
end

private
Expand Down
20 changes: 20 additions & 0 deletions lib/omnes/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,24 @@ def default_message
MSG
end
end

# Raised when a subscription is not known by a bus
class UnknownSubscriptionError < Error
attr_reader :subscription, :bus

def initialize(subscription:, bus:)
@subscription = subscription
@bus = bus
super(default_message)
end

private

def default_message
<<~MSG
#{subscription.inspect} is not a subscription known by bus
#{bus.inspect}
MSG
end
end
end
75 changes: 56 additions & 19 deletions spec/support/shared_examples/bus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -360,41 +360,78 @@ def bar
end
end

describe "#with_subscriptions" do
it "returns a new instance with given subscriptions" do
describe "#performing_only" do
it "runs given subcriptions" do
bus = subject.new
dummy1, dummy2, dummy3 = Array.new(3) { counter.new }
bus.register(:foo)
subscription1 = bus.subscribe(:foo) { dummy1.inc }
subscription2 = bus.subscribe(:foo) { dummy2.inc }
bus.subscribe(:foo) { dummy3.inc }
dummy = counter.new
subscription = bus.subscribe(:foo) { dummy.inc }

new_bus = bus.with_subscriptions([subscription1, subscription2])
new_bus.publish(:foo)
bus.performing_only(subscription) do
bus.publish(:foo)
end

expect(new_bus).not_to eq(bus)
expect(new_bus.subscriptions).to match_array([subscription1, subscription2])
expect(dummy1.count).to be(1)
expect(dummy2.count).to be(1)
expect(dummy3.count).to be(0)
expect(dummy.count).to be(1)
end

it "keeps the same registry" do
it "doesn't run excluded subcriptions" do
bus = subject.new
bus.register(:foo)
dummy = counter.new
bus.subscribe(:foo) { dummy.inc }

bus.performing_only do
bus.publish(:foo)
end

expect(dummy.count).to be(0)
end

it "can run excluded subcriptions when the block is over" do
bus = subject.new
bus.register(:foo)
dummy = counter.new
bus.subscribe(:foo) { dummy.inc }

new_bus = bus.with_subscriptions([])
bus.performing_only do
bus.publish(:foo)
end

expect(dummy.count).to be(0)

expect(new_bus.registry).to be(bus.registry)
bus.publish(:foo)

expect(dummy.count).to be(1)
end

it "caller location start is 1" do
it "restores old subcriptions when an exception is raised" do
bus = subject.new
bus.register(:foo)
dummy = counter.new
subscription = bus.subscribe(:foo) { raise "error" }
bus.subscribe(:foo) { dummy.inc }

bus.performing_only(subscription) do
expect do
bus.publish(:foo)
end.to raise_error(RuntimeError)
end

bus.unsubscribe(subscription)
bus.publish(:foo)

expect(dummy.count).to be(1)
end

new_bus = bus.with_subscriptions([])
it "raises an error when the subscription is now known" do
bus1 = subject.new
bus2 = subject.new
bus2.register(:foo)
subscription = bus2.subscribe(:foo)

expect(new_bus.cal_loc_start).to be(1)
expect do
bus1.performing_only(subscription)
end.to raise_error(Omnes::UnknownSubscriptionError)
end
end
end

0 comments on commit 0c8dc16

Please sign in to comment.