rspec-context-let 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []