Skip to content

Commit

Permalink
Merge pull request #91 from sleepingkingstudios/fix/wrap-deferred
Browse files Browse the repository at this point in the history
Fix/Pass #wrap_deferred block to example group.
  • Loading branch information
sleepingkingstudios authored Jul 16, 2024
2 parents 25f4667 + 245d657 commit 11de89a
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 15 deletions.
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1037,12 +1037,21 @@ end

### Parameterized Examples

Deferred examples can also be defined with parameters using the `Deferred::Provider` DSL.
Deferred examples can also be defined with parameters using the `Deferred::Provider` DSL. Defining a parameterized example group allows for defining and sharing specs that describe complex and conditional behavior.

For deferred specs that set up a context rather than examples, `deferred_context` is provided as an alias to `Provider.deferred_examples`.

```ruby
module VehicleExamples
include RSpec::SleepingKingStudios::Deferred::Provider

deferred_context 'when the vehicle needs to be serviced' \
do |serviced_at: '2020-01-01'|
before(:example) do
vehicle.last_serviced_at = serviced_at
end
end

deferred_examples 'should be a Vehicle' do |vehicle_type:|
it { expect(subject).to be_a Spec::Models::Vehicle }

Expand All @@ -1051,7 +1060,11 @@ module VehicleExamples
end
end
end
```

The deferred examples can be included in example groups using the `Deferred::Consumer` DSL.

```ruby
RSpec.describe Car do
include RSpec::SleepingKingStudios::Deferred::Consumer
include VehicleExamples
Expand All @@ -1071,9 +1084,22 @@ RSpec.describe Rocket do
end
```

Defining a parameterized example group allows for defining and sharing specs that describe complex and conditional behavior.
`Deferred::Consumer` also defines support for wrapped deferred examples, which automatically generate a new context and include the deferred examples in the new example group. If `#wrap_deferred` is passed a block, that block will automatically be evaluated in the context of the example group, allowing you to define additional context or examples.

For deferred specs that set up a context rather than examples, `deferred_context` is provided as an alias to `Provider.deferred_examples`.
```ruby
RSpec.describe Car do
include RSpec::SleepingKingStudios::Deferred::Consumer
include VehicleExamples

subject(:car) { described_class.new }

wrap_deferred 'when the vehicle needs to be serviced' do
it { expect(car.last_serviced_at).to be == '2020-01-01' }
end
end
```

Finally, `Deferred::Consumer` includes the shortcuts `#finclude_deferred` and `#fwrap_deferred` to automatically focus deferred examples, or `#xinclude_deferred` and `#xwrap_deferred` to skip deferred examples.

## Shared Examples

Expand Down
42 changes: 33 additions & 9 deletions lib/rspec/sleeping_king_studios/deferred/consumer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,21 @@ def finclude_deferred(description, ...)
# @overload fwrap_deferred(description, *arguments, **keywords, &block)
# Includes the deferred examples inside a focused example group.
#
# Unlike #include_deferred, a block parameter will be included in the
# created example group, not passed to the deferred examples. To wrap
# deferred examples that require a block, create the example group
# separately and call #include_deferred.
#
# @param description [String] the name of the deferred examples.
# @param arguments [Array] arguments passed to the deferred examples.
# @param keywords [Hash] keywords passed to the deferred examples.
# @param block [Block] a block passed to the deferred examples.
def fwrap_deferred(description, ...)
# @param block [Block] additional examples to be evaluated inside the
# example group.
def fwrap_deferred(description, *args, **kwargs, &block)
fdescribe "(focused) #{description}" do
include_deferred(description, ...)
include_deferred(description, *args, **kwargs)

instance_exec(&block) if block_given?
end
end

Expand All @@ -60,13 +68,21 @@ def include_deferred(description, ...)
# @overload wrap_deferred(description, *arguments, **keywords, &block)
# Includes the deferred examples inside an example group.
#
# Unlike #include_deferred, a block parameter will be included in the
# created example group, not passed to the deferred examples. To wrap
# deferred examples that require a block, create the example group
# separately and call #include_deferred.
#
# @param description [String] the name of the deferred examples.
# @param arguments [Array] arguments passed to the deferred examples.
# @param keywords [Hash] keywords passed to the deferred examples.
# @param block [Block] a block passed to the deferred examples.
def wrap_deferred(description, ...)
# @param block [Block] additional examples to be evaluated inside the
# example group.
def wrap_deferred(description, *args, **kwargs, &block)
describe description do
include_deferred(description, ...)
include_deferred(description, *args, **kwargs)

instance_exec(&block) if block_given?
end
end

Expand All @@ -86,13 +102,21 @@ def xinclude_deferred(description, ...)
# @overload xwrap_deferred(description, *arguments, **keywords, &block)
# Includes the deferred examples inside a skipped example group.
#
# Unlike #include_deferred, a block parameter will be included in the
# created example group, not passed to the deferred examples. To wrap
# deferred examples that require a block, create the example group
# separately and call #include_deferred.
#
# @param description [String] the name of the deferred examples.
# @param arguments [Array] arguments passed to the deferred examples.
# @param keywords [Hash] keywords passed to the deferred examples.
# @param block [Block] a block passed to the deferred examples.
def xwrap_deferred(description, ...)
# @param block [Block] additional examples to be evaluated inside the
# example group.
def xwrap_deferred(description, *args, **kwargs, &block)
xdescribe "(skipped) #{description}" do
include_deferred(description, ...)
include_deferred(description, *args, **kwargs)

instance_exec(&block) if block_given?
end
end

Expand Down
35 changes: 35 additions & 0 deletions spec/integration/deferred/wrapped_contexts_spec.fixture.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require 'support/integration/deferred/wrapped_contexts'

RSpec.describe Spec::Models::Rocket do
include RSpec::SleepingKingStudios::Deferred::Consumer
include Spec::Integration::Deferred::WrappedContexts

subject(:rocket) { described_class.new('Imp IV') }

wrap_deferred 'when the rocket has been launched' do
it { expect(rocket.launched?).to be true }

wrap_deferred 'when the payload includes a booster' do
let(:expected_payload) { { booster: true } }

it { expect(rocket.payload).to be == expected_payload }
end

wrap_deferred 'when the payload includes a satellite' do
let(:expected_payload) { { satellite: true } }

it { expect(rocket.payload).to be == expected_payload }
end

context 'when the rocket has multiple payloads' do
include_deferred 'when the payload includes a booster'
include_deferred 'when the payload includes a satellite'

let(:expected_payload) { { booster: true, satellite: true } }

it { expect(rocket.payload).to be == expected_payload }
end
end
end
28 changes: 28 additions & 0 deletions spec/integration/deferred/wrapped_contexts_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

require 'rspec/sleeping_king_studios/deferred/examples'

require 'support/sandbox'

RSpec.describe RSpec::SleepingKingStudios::Deferred::Examples do
let(:fixture_file) do
%w[spec/integration/deferred/wrapped_contexts_spec.fixture.rb]
end
let(:result) do
Spec::Support::Sandbox.run(fixture_file)
end
let(:expected_examples) do
<<~EXAMPLES.lines.map(&:strip)
Spec::Models::Rocket when the rocket has been launched is expected to equal true
Spec::Models::Rocket when the rocket has been launched when the payload includes a booster is expected to be == {:booster=>true}
Spec::Models::Rocket when the rocket has been launched when the payload includes a satellite is expected to be == {:satellite=>true}
Spec::Models::Rocket when the rocket has been launched when the rocket has multiple payloads is expected to be == {:booster=>true, :satellite=>true}
EXAMPLES
end

it 'should apply the deferred examples', :aggregate_failures do
expect(result.summary).to be == '4 examples, 0 failures'

expect(result.example_descriptions).to be == expected_examples
end
end
30 changes: 30 additions & 0 deletions spec/support/integration/deferred/wrapped_contexts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require 'rspec/sleeping_king_studios'

require 'support/integration/deferred'

module Spec::Integration::Deferred
module WrappedContexts
include RSpec::SleepingKingStudios::Deferred::Provider

module WhenTheRocketHasBeenLaunchedContext
include RSpec::SleepingKingStudios::Deferred::Examples

let?(:launch_site) { 'KSC' }
let?(:payload) { {} }

before(:example) do
subject.launch(launch_site:, payload:)
end
end

deferred_context 'when the payload includes a booster' do
let(:payload) { super().merge(booster: true) }
end

deferred_context 'when the payload includes a satellite' do
let(:payload) { super().merge(satellite: true) }
end
end
end
72 changes: 69 additions & 3 deletions spec/support/shared_examples/deferred_registry_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -676,20 +676,71 @@ module DeferredRegistryExamples
allow(child_example_group).to receive(:include_deferred)
end

it 'should wrap the deferred examples in a focused example group',
:aggregate_failures \
do
it 'should define an example group' do
described_class.public_send(method_name, description)

expect(described_class)
.to have_received(example_group_method)
.with(example_group_name.gsub(':description', description))
end

it 'should include the deferred examples in the example group' do
described_class.public_send(method_name, description)

expect(child_example_group)
.to have_received(:include_deferred)
.with(description)
end
end

shared_examples 'should wrap the deferred examples and the block' \
do |example_group_method:, example_group_name:, method_name:|
let(:description) { 'should do something' }
let(:examples) do
lambda do
specify 'should include the examples'
end
end
let(:child_example_group) do
Spec::Support.isolated_example_group do
include RSpec::SleepingKingStudios::Deferred::Consumer
end
end

before(:example) do
allow(described_class).to receive(example_group_method) do |_, &block|
child_example_group.instance_exec(&block)
end

allow(child_example_group).to receive(:include_deferred)
allow(child_example_group).to receive(:specify)
end

it 'should define an example group' do
described_class.public_send(method_name, description, &examples)

expect(described_class)
.to have_received(example_group_method)
.with(example_group_name.gsub(':description', description))
end

it 'should include the deferred examples in the example group' do
described_class.public_send(method_name, description, &examples)

expect(child_example_group)
.to have_received(:include_deferred)
.with(description)
end

it 'should include the examples block in the example group' do
described_class.public_send(method_name, description, &examples)

expect(child_example_group)
.to have_received(:specify)
.with('should include the examples')
end
end

describe '.finclude_deferred' do
it 'should define the class method' do
expect(described_class)
Expand Down Expand Up @@ -720,6 +771,11 @@ module DeferredRegistryExamples
example_group_method: :fdescribe,
example_group_name: '(focused) :description',
method_name: :fwrap_deferred

include_examples 'should wrap the deferred examples and the block',
example_group_method: :fdescribe,
example_group_name: '(focused) :description',
method_name: :fwrap_deferred
end

describe '.include_deferred' do
Expand Down Expand Up @@ -920,6 +976,11 @@ module DeferredRegistryExamples
example_group_method: :describe,
example_group_name: ':description',
method_name: :wrap_deferred

include_examples 'should wrap the deferred examples and the block',
example_group_method: :describe,
example_group_name: ':description',
method_name: :wrap_deferred
end

describe '.xinclude_deferred' do
Expand Down Expand Up @@ -952,6 +1013,11 @@ module DeferredRegistryExamples
example_group_method: :xdescribe,
example_group_name: '(skipped) :description',
method_name: :xwrap_deferred

include_examples 'should wrap the deferred examples and the block',
example_group_method: :xdescribe,
example_group_name: '(skipped) :description',
method_name: :xwrap_deferred
end
end
end
Expand Down

0 comments on commit 11de89a

Please sign in to comment.