Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit 461144b

Browse files
committed
Add config.when_first_matching_example_defined.
1 parent 46d2214 commit 461144b

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed

Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Enhancements:
99
(Myron Marston, #2189)
1010
* `RSpec::Core::Configuration#reporter` is now public API under semver.
1111
(Jon Rowe, #2193)
12+
* Add new `config.when_first_matching_example_defined` hook. (Myron
13+
Marston, #2175)
1214

1315
### 3.5.0.beta1 / 2016-02-06
1416
[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.4.3...v3.5.0.beta1)

features/.nav

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- before_and_after_hooks.feature
2121
- around_hooks.feature
2222
- filtering.feature
23+
- when_first_matching_example_defined.feature
2324
- subject:
2425
- implicit_subject.feature
2526
- explicit_subject.feature
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
@wip @announce
2+
Feature: `when_first_matching_example_defined` hook
3+
4+
In large projects that use RSpec, it's common to have some expensive setup logic
5+
that is only needed when certain kinds of specs have been loaded. If that kind of
6+
spec has not been loaded, you'd prefer to avoid the cost of doing the setup.
7+
8+
The `when_first_matching_example_defined` hook makes it easy to conditionally
9+
perform some logic when the first example is defined with matching metadata,
10+
allowing you to ensure the necessary setup is performed only when needed.
11+
12+
Background:
13+
Given a file named "spec/spec_helper.rb" with:
14+
"""ruby
15+
RSpec.configure do |config|
16+
config.when_first_matching_example_defined(:db) do
17+
require "support/db"
18+
end
19+
end
20+
"""
21+
And a file named "spec/support/db.rb" with:
22+
"""ruby
23+
RSpec.configure do |config|
24+
config.before(:suite) do
25+
puts "Bootstrapped the DB."
26+
end
27+
28+
config.around(:example, :db) do |example|
29+
puts "Starting a DB transaction."
30+
example.run
31+
puts "Rolling back a DB transaction."
32+
end
33+
end
34+
"""
35+
And a file named ".rspec" with:
36+
"""
37+
--require spec_helper
38+
"""
39+
And a file named "spec/unit_spec.rb" with:
40+
"""
41+
RSpec.describe "A unit spec" do
42+
it "does not require a database" do
43+
puts "in unit example"
44+
end
45+
end
46+
"""
47+
And a file named "spec/integration_spec.rb" with:
48+
"""
49+
RSpec.describe "An integration spec", :db do
50+
it "requires a database" do
51+
puts "in integration example"
52+
end
53+
end
54+
"""
55+
56+
Scenario: Running the entire suite loads the DB setup
57+
When I run `rspec`
58+
Then it should pass with:
59+
"""
60+
Bootstrapped the DB.
61+
Starting a DB transaction.
62+
in integration example
63+
Rolling back a DB transaction.
64+
.in unit example
65+
.
66+
"""
67+
68+
Scenario: Running just the unit spec does not load the DB setup
69+
When I run `rspec spec/unit_spec.rb`
70+
Then the examples should all pass
71+
And the output should not contain "DB"

lib/rspec/core/configuration.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,6 +1553,41 @@ def define_derived_metadata(*filters, &block)
15531553
@derived_metadata_blocks.append(block, meta)
15541554
end
15551555

1556+
# Defines a callback that runs after the first example with matching
1557+
# metadata is defined. If no examples are defined with matching metadata,
1558+
# it will not get called at all.
1559+
#
1560+
# This can be used to ensure some setup is performed (such as bootstrapping
1561+
# a DB or loading a specific file that adds significantly to the boot time)
1562+
# if needed (as indicated by the presence of an example with matching metadata)
1563+
# but avoided otherwise.
1564+
#
1565+
# @example
1566+
# RSpec.configure do |config|
1567+
# config.when_first_matching_example_defined(:db) do
1568+
# # Load a support file that does some heavyweight setup,
1569+
# # including bootstrapping the DB, but only if we have loaded
1570+
# # any examples tagged with `:db`.
1571+
# require 'support/db'
1572+
# end
1573+
# end
1574+
def when_first_matching_example_defined(*filters, &block)
1575+
specified_meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering)
1576+
1577+
callback = lambda do |example_or_group_meta|
1578+
# Example groups do not have `:example_group` metadata
1579+
# (instead they have `:parent_example_group` metadata).
1580+
return unless example_or_group_meta.key?(:example_group)
1581+
1582+
# Ensure the callback only fires once.
1583+
@derived_metadata_blocks.items_for(specified_meta).delete(callback)
1584+
1585+
block.call
1586+
end
1587+
1588+
@derived_metadata_blocks.append(callback, specified_meta)
1589+
end
1590+
15561591
# @private
15571592
def apply_derived_metadata_to(metadata)
15581593
@derived_metadata_blocks.items_for(metadata).each do |block|

spec/rspec/core/configuration_spec.rb

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,111 @@ def exclude?(line)
15981598
end
15991599
end
16001600

1601+
describe "#when_first_matching_example_defined" do
1602+
include_examples "warning of deprecated `:example_group` during filtering configuration", :when_first_matching_example_defined
1603+
1604+
it "runs the block when the first matching example is defined" do
1605+
sequence = []
1606+
RSpec.configuration.when_first_matching_example_defined(:foo) do
1607+
sequence << :callback
1608+
end
1609+
1610+
RSpec.describe do
1611+
example("ex 1")
1612+
sequence << :before_first_matching_example_defined
1613+
example("ex 2", :foo)
1614+
sequence << :after_first_matching_example_defined
1615+
end
1616+
1617+
expect(sequence).to eq [:before_first_matching_example_defined, :callback, :after_first_matching_example_defined]
1618+
end
1619+
1620+
it "does not fire when later matching examples are defined" do
1621+
sequence = []
1622+
RSpec.configuration.when_first_matching_example_defined(:foo) do
1623+
sequence << :callback
1624+
end
1625+
1626+
RSpec.describe do
1627+
example("ex 1", :foo)
1628+
sequence.clear
1629+
1630+
sequence << :before_second_matching_example_defined
1631+
example("ex 2", :foo)
1632+
sequence << :after_second_matching_example_defined
1633+
end
1634+
1635+
expect(sequence).to eq [:before_second_matching_example_defined, :after_second_matching_example_defined]
1636+
end
1637+
1638+
it "does not run the block if no matching examples are defined" do
1639+
sequence = []
1640+
RSpec.configuration.when_first_matching_example_defined(:foo) do
1641+
sequence << :callback
1642+
end
1643+
1644+
RSpec.describe do
1645+
example("ex 1")
1646+
example("ex 2", :bar)
1647+
end
1648+
1649+
expect(sequence).to eq []
1650+
end
1651+
1652+
it 'does not run the block if groups match the metadata but no examples do' do
1653+
called = false
1654+
RSpec.configuration.when_first_matching_example_defined(:foo => true) do
1655+
called = true
1656+
end
1657+
1658+
RSpec.describe "group 1", :foo => true do
1659+
end
1660+
1661+
RSpec.describe "group 2", :foo => true do
1662+
example("ex", :foo => false)
1663+
end
1664+
1665+
expect(called).to be false
1666+
end
1667+
1668+
it "still runs after the first matching example even if there is a group that matches earlier" do
1669+
sequence = []
1670+
RSpec.configuration.when_first_matching_example_defined(:foo) do
1671+
sequence << :callback
1672+
end
1673+
1674+
RSpec.describe "group", :foo do
1675+
end
1676+
1677+
RSpec.describe do
1678+
example("ex 1")
1679+
sequence << :before_first_matching_example_defined
1680+
example("ex 2", :foo)
1681+
sequence << :after_first_matching_example_defined
1682+
end
1683+
1684+
expect(sequence).to eq [:before_first_matching_example_defined, :callback, :after_first_matching_example_defined]
1685+
end
1686+
1687+
context "when a group is defined with matching metadata" do
1688+
it "runs the callback after the first example in the group is defined" do
1689+
sequence = []
1690+
RSpec.configuration.when_first_matching_example_defined(:foo) do
1691+
sequence << :callback
1692+
end
1693+
1694+
sequence << :before_group
1695+
RSpec.describe "group", :foo do
1696+
sequence << :before_example
1697+
example("ex")
1698+
sequence << :after_example
1699+
end
1700+
1701+
expect(sequence).to eq [:before_group, :before_example, :callback, :after_example]
1702+
end
1703+
end
1704+
end
1705+
16011706
describe "#add_setting" do
16021707
describe "with no modifiers" do
16031708
context "with no additional options" do

0 commit comments

Comments
 (0)