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.
@@ -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: []