rspec-context-let 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +147 -0
- data/lib/rspec-context-let.rb +1 -0
- data/lib/rspec/context-let.rb +38 -0
- metadata +134 -0
data/README.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# Context-scope "let" for RSpec
|
2
|
+
|
3
|
+
Do you love how RSpec's `let` method allows you to DRY up your tests and
|
4
|
+
clean up your code? Do you hate how slow your tests get when they re-run
|
5
|
+
the operation under test over, and over, and over again? Does the conflict
|
6
|
+
between these two feelings cause you to break out in hives?
|
7
|
+
|
8
|
+
Well, put away the antihistamines, because `rspec-context-let` has the
|
9
|
+
answer to your prayers:
|
10
|
+
|
11
|
+
require 'rspec-context-let'
|
12
|
+
|
13
|
+
describe MyAPI do
|
14
|
+
context "when called" do
|
15
|
+
clet(:response) do
|
16
|
+
MyAPI.call
|
17
|
+
end
|
18
|
+
|
19
|
+
it "makes a response" do
|
20
|
+
expect(response).to_not be(nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns a hash" do
|
24
|
+
expect(response).to be_a(Hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns at least three records" do
|
28
|
+
expect(response.length).to be >= 3
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
By wrapping your expensive operations in a `clet` ("Context LET") call,
|
34
|
+
rather than a regular `let` call, the result will be cached for the
|
35
|
+
entireity of that context's existence, rather than being recalculated for
|
36
|
+
every example. Other than that little detail, a variable set by `clet`
|
37
|
+
should work pretty much identically to a variable set by `let` -- it's
|
38
|
+
available in all your examples, it's available in sub-contexts (and won't be
|
39
|
+
re-run in those sub-contexts), and it's available in shared example groups
|
40
|
+
(if you rely on `let`ted variables in there, which I don't really
|
41
|
+
recommend).
|
42
|
+
|
43
|
+
If you're wondering why this useful, consider what happens if the above
|
44
|
+
`MyAPI.call` takes, say, 100ms to run. In that case, using `clet` instead
|
45
|
+
of `let` has just saved you 200ms every time you run the above test cases.
|
46
|
+
Multiply that by the 15 or 20 examples you might actually have for a given
|
47
|
+
piece of test code, and the dozens or hundreds of times a day you run your
|
48
|
+
test suites, and damn it adds up. As a real-world testimonial, the test
|
49
|
+
suite in which `clet` was first developed had 405 examples at the time;
|
50
|
+
before `clet`, it took an average of **20.54 seconds** to run; afterwards,
|
51
|
+
it took **7.66 seconds**. (Averages taken from 25 runs of each suite on an
|
52
|
+
otherwise idle machine)
|
53
|
+
|
54
|
+
Despite the indisputable awesomeness of the above, everything isn't *quite*
|
55
|
+
perfection. With the code under test only being run once, anything that
|
56
|
+
gets reset between examples can't be examined to verify the tested code did
|
57
|
+
the right thing. Using instance variables, for example, probably won't do
|
58
|
+
what you want:
|
59
|
+
|
60
|
+
require 'rspec-context-let'
|
61
|
+
|
62
|
+
describe MyAPI do
|
63
|
+
context "when called" do
|
64
|
+
clet(:response) do
|
65
|
+
@prev_value = MyAPI.get_value
|
66
|
+
MyAPI.call
|
67
|
+
end
|
68
|
+
|
69
|
+
it "makes a response" do
|
70
|
+
expect(response).to_not be(nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "changes value" do
|
74
|
+
# THIS WILL FAIL SPECTACULARLY
|
75
|
+
expect(MyAPI.get_value).to_not eq(@prev_value)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
The problem here is that `@prev_value` will get set when the block passed to
|
81
|
+
`clet` runs... which will be for the `"makes a response"` example. By the
|
82
|
+
time the `"changes value"` example runs, that instance variable is dead and
|
83
|
+
buried.
|
84
|
+
|
85
|
+
Another problem that has bitten me in the past is using DB transactions to
|
86
|
+
clean up database changes after every example:
|
87
|
+
|
88
|
+
require 'rspec-context-let'
|
89
|
+
|
90
|
+
RSpec.configure do |c|
|
91
|
+
c.around do |example|
|
92
|
+
DB.transaction(
|
93
|
+
:rollback => :always,
|
94
|
+
:auto_savepoint => true
|
95
|
+
) { example.run }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe MyAPI do
|
100
|
+
context "when called" do
|
101
|
+
clet(:response) do
|
102
|
+
MyAPI.call
|
103
|
+
end
|
104
|
+
|
105
|
+
it "makes a response" do
|
106
|
+
expect(response).to_not be(nil)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "does something to the database" do
|
110
|
+
# THIS WON'T END WELL EITHER
|
111
|
+
expect(DB[:dataz].first.id).to eq("d00d")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
This fails for much the same reason as the instance variable case. The
|
117
|
+
database got changed when `MyAPI.call` ran, but at the end of that first
|
118
|
+
example the DB transaction got rolled back and the change was no longer
|
119
|
+
there when the second example ran.
|
120
|
+
|
121
|
+
Depending on your circumstances, you should either use a regular `let` in
|
122
|
+
these circumstances, or else wrap the call to the code under test into the
|
123
|
+
same `clet` call as captures the data you wish to examine. In the database
|
124
|
+
case, you might do that with:
|
125
|
+
|
126
|
+
require 'rspec-context-let'
|
127
|
+
|
128
|
+
describe MyAPI do
|
129
|
+
context "when called" do
|
130
|
+
clet(:response) do
|
131
|
+
MyAPI.call
|
132
|
+
end
|
133
|
+
|
134
|
+
clet(:dbdata) do
|
135
|
+
MyAPI.call
|
136
|
+
DB[:dataz].first
|
137
|
+
end
|
138
|
+
|
139
|
+
it "makes a response" do
|
140
|
+
expect(response).to_not be(nil)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "does something to the database" do
|
144
|
+
expect(dbdata.id).to eq("d00d")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "rspec/context-let"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rspec/core'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module ContextLet
|
5
|
+
module ClassMethods
|
6
|
+
class UnevaluatedValue; end
|
7
|
+
|
8
|
+
def clet(name, &block)
|
9
|
+
raise "#clet called without a block" if block.nil?
|
10
|
+
name = name.to_sym
|
11
|
+
|
12
|
+
RSpec::Core::MemoizedHelpers.module_for(self).send(:define_method, name, &block)
|
13
|
+
|
14
|
+
@__context_memo ||= {}
|
15
|
+
@__context_memo[name] = [UnevaluatedValue, block]
|
16
|
+
|
17
|
+
define_method(name) do
|
18
|
+
klass = self.class
|
19
|
+
|
20
|
+
until klass.nil?
|
21
|
+
cm = klass.instance_variable_get(:@__context_memo)
|
22
|
+
if cm.is_a?(Hash) and cm.has_key?(name)
|
23
|
+
if cm[name].is_a?(Array) and cm[name][0] == UnevaluatedValue
|
24
|
+
cm[name] = super(&nil)
|
25
|
+
return cm[name]
|
26
|
+
else
|
27
|
+
return cm[name]
|
28
|
+
end
|
29
|
+
else
|
30
|
+
rklass = klass.name.reverse.split("::", 2)[1]
|
31
|
+
klass = rklass.nil? ? nil : eval(rklass.reverse)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-context-let
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Palmer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec-core
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.13'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.13'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: git-version-bump
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rdoc
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description:
|
95
|
+
email:
|
96
|
+
executables: []
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files:
|
99
|
+
- README.md
|
100
|
+
files:
|
101
|
+
- README.md
|
102
|
+
- lib/rspec/context-let.rb
|
103
|
+
- lib/rspec-context-let.rb
|
104
|
+
homepage: http://theshed.hezmatt.org/giddyup
|
105
|
+
licenses: []
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
hash: -1635824792618978907
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
segments:
|
126
|
+
- 0
|
127
|
+
hash: -1635824792618978907
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 1.8.23
|
131
|
+
signing_key:
|
132
|
+
specification_version: 3
|
133
|
+
summary: ! '''git-deploy'' command to interact with giddyup-managed deployments'
|
134
|
+
test_files: []
|