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

Commit 85897da

Browse files
committed
Add config.when_first_matching_example_defined.
1 parent 46d2214 commit 85897da

File tree

5 files changed

+213
-0
lines changed

5 files changed

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