Skip to content

Commit a8cb2ab

Browse files
committed
✨ RSpec shared context: "with rake"
1 parent 2917111 commit a8cb2ab

7 files changed

Lines changed: 172 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Please file a bug if you notice a violation of semantic versioning.
1818

1919
## [Unreleased]
2020
### Added
21+
- RSpec shared context: "with rake"
2122
### Changed
2223
### Deprecated
2324
### Removed

README.md

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,19 @@ and concordant releases of JRuby, and TruffleRuby.
3838

3939
## 💡 Info you can shake a stick at
4040

41-
| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
42-
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
43-
| Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
44-
| Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i] <br/> [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
45-
| Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
46-
| Works with MRI Ruby 2 | <br/> [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] |
47-
| Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
48-
| Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![Wiki][📜wiki-img]][📜wiki] |
49-
| Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
50-
| Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
51-
| Support | [![Live Chat on Discord][✉️discord-invite-img]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
52-
| Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
53-
| `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
41+
| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
42+
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
43+
| Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
44+
| Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i] <br/> [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
45+
| Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
46+
| Works with MRI Ruby 2 | [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] |
47+
| Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
48+
| Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![Wiki][📜wiki-img]][📜wiki] |
49+
| Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
50+
| Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
51+
| Support | [![Live Chat on Discord][✉️discord-invite-img]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
52+
| Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
53+
| `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
5454

5555
### Compatibility
5656

@@ -251,6 +251,26 @@ end
251251

252252
## 🔧 Basic Usage
253253

254+
### Shared Context "with rake"
255+
256+
Make sure to require "rake" before you require "kettle/test" (or "kettle/dev" since it will require this library).
257+
258+
If you do the shared context will be available to use in your specs automatically.
259+
260+
```ruby
261+
include_context "with rake", "demo" do
262+
let(:tmp_rakelib) do
263+
Dir.mktmpdir("with_rake_spec_")
264+
end
265+
let(:task_dir) { tmp_rakelib }
266+
let(:rakelib) { tmp_rakelib }
267+
# Provide args for the rake task invocation
268+
let(:task_args) { ["Bob"] }
269+
end
270+
```
271+
272+
See the spec for a more complete example: See for more details: [with_rake_spec.rb](spec/kettle/text/support/shared_contexts/with_rake_spec.rb)
273+
254274
### RSpec Time Machine Tags :freeze and :travel
255275

256276
Timecop.travel/freeze any RSpec (describe|context|example) with

lib/kettle/test/config/int/rspec/timecop_rspec.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
require "timecop/rspec"
2-
31
# Ensure a consistent time for all tests
42
#
53
# Integrates timecop-rspec and provides metadata-based time travel/freeze.

lib/kettle/test/external.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
require "rspec/block_is_expected" # Usage: see https://github.com/galtzo-floss/rspec-block_is_expected#example
2323
require "rspec_junit_formatter"
2424
require "silent_stream"
25+
require "timecop/rspec"
2526

2627
# Configs of external libraries
2728
require_relative "config/version_gem"

lib/kettle/test/rspec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
require "kettle/test"
66

77
require_relative "internal"
8+
9+
# A gem's test harness should do require "rake" if it is their dependency,
10+
# and they define a rake task they want to test.
11+
require_relative "support/shared_contexts/with_rake" if defined?(Rake)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
# rubocop:disable RSpec/MultipleMemoizedHelpers
4+
5+
# Inspired by: https://thoughtbot.com/blog/test-rake-tasks-like-a-boss
6+
# This version doesn't require a Rails app!
7+
# This file will only be required if Rake is defined
8+
# require "rake"
9+
10+
# Usage:
11+
# include_context "with rake", "demo" do
12+
# let(:tmp_rakelib) do
13+
# Dir.mktmpdir("with_rake_spec_")
14+
# end
15+
# # Required by the shared context (override the raising defaults)
16+
# let(:task_dir) { tmp_rakelib }
17+
# let(:rakelib) { tmp_rakelib }
18+
# # Provide args for the rake task invocation
19+
# let(:task_args) { ["Bob"] }
20+
# end
21+
#
22+
# See for more details: spec/kettle/text/support/shared_contexts/with_rake_spec.rb
23+
#
24+
RSpec.shared_context("with rake") do |task_base_name|
25+
let(:rake_app) { Rake::Application.new }
26+
let(:task_name) { self.class.top_level_description.sub(/\Arake /, "") }
27+
# A gem depending on this gem, and using this shared context must define task_dir
28+
# let(:task_dir) { "lib/gem_checksums/rakelib" }
29+
let(:task_dir) { raise ArgumentError, "define task_dir in the block of `include_context 'with rake', basename do ... end`" }
30+
let(:task_args) { [] }
31+
let(:invoke) { rake_task.invoke(*task_args) }
32+
# A gem depending on this gem, and using this shared context must define rakelib
33+
# let(:rakelib) { File.join(__dir__, "..", "..", "..", task_dir) }
34+
let(:rakelib) { raise ArgumentError, "define rakelib in the block of `include_context 'with rake', basename do ... end`" }
35+
let(:rake_task) { Rake::Task[task_name] }
36+
37+
include_context "with stubbed env"
38+
39+
def loaded_files_excluding_current_rake_file(task_base_name)
40+
$".reject { |file| file == File.join(rakelib, "#{task_base_name}.rake").to_s }
41+
end
42+
43+
before do
44+
Rake.application = rake_app
45+
Rake.application.rake_require(task_base_name, [rakelib], loaded_files_excluding_current_rake_file(task_base_name))
46+
end
47+
end
48+
# rubocop:enable RSpec/MultipleMemoizedHelpers
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# frozen_string_literal: true
2+
3+
# We need Rake loaded so that kettle/test/rspec (and thus the shared context) can be required.
4+
require "rake"
5+
# In case spec_helper loaded kettle/test/rspec before Rake was defined, load the context directly here.
6+
require "kettle/test/support/shared_contexts/with_rake"
7+
8+
RSpec.describe "rake demo:print" do # rubocop:disable RSpec/DescribeClass
9+
context "with a temporary rakelib and a demo task", :check_output do
10+
require "tmpdir"
11+
12+
# Include the shared context first so that our overriding lets below take precedence.
13+
include_context "with rake", "demo" do
14+
let(:tmp_rakelib) do
15+
Dir.mktmpdir("with_rake_spec_")
16+
end
17+
# Required by the shared context (override the raising defaults)
18+
let(:task_dir) { tmp_rakelib }
19+
let(:rakelib) { tmp_rakelib }
20+
# Provide args for the rake task invocation
21+
let(:task_args) { ["Bob"] }
22+
end
23+
24+
around do |example|
25+
require "fileutils"
26+
# Create a rake file BEFORE the shared context's before(:each) runs
27+
File.open(File.join(tmp_rakelib, "demo.rake"), "w") do |f|
28+
f.write(<<~'RAKE')
29+
namespace :demo do
30+
desc "Print a greeting"
31+
task :print, [:name] do |_t, args|
32+
puts "Hello #{args[:name] || 'world'}"
33+
end
34+
end
35+
RAKE
36+
end
37+
38+
begin
39+
example.run
40+
ensure
41+
# Cleanup tasks loaded into the Rake application for isolation
42+
Rake::Task.tasks.each { |t| t.clear }
43+
# Remove the temp directory and its contents
44+
FileUtils.rm_rf(tmp_rakelib) if File.directory?(tmp_rakelib)
45+
end
46+
end
47+
48+
it "derives task_name from the top-level description by stripping the 'rake ' prefix" do
49+
expect(task_name).to eq("demo:print")
50+
end
51+
52+
it "sets Rake.application to the isolated rake_app instance" do
53+
expect(Rake.application).to be(rake_app)
54+
end
55+
56+
it "loads the rake file and defines the target task" do
57+
expect(rake_task).to be_a(Rake::Task)
58+
expect(rake_task.name).to eq("demo:print")
59+
end
60+
61+
it "invokes the task with provided arguments and prints expected output" do
62+
expect { invoke }.to output(/Hello Bob/).to_stdout
63+
end
64+
65+
it "excludes the current rake file from the loaded files list via helper" do
66+
required_path = File.join(rakelib, "demo.rake").to_s
67+
68+
# Simulate the scenario where the rake file is already in $LOADED_FEATURES
69+
added = false
70+
unless $LOADED_FEATURES.include?(required_path)
71+
$LOADED_FEATURES << required_path
72+
added = true
73+
end
74+
75+
begin
76+
excluded = loaded_files_excluding_current_rake_file("demo")
77+
# The helper should exclude exactly this file path
78+
expect(excluded).not_to include(required_path)
79+
ensure
80+
# Clean up our simulation
81+
$LOADED_FEATURES.delete(required_path) if added
82+
end
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)