active_remote-cached 1.1.1 → 1.2.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.
@@ -3,30 +3,196 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe ::ActiveRemote::Cached::Cache do
6
+ let(:invalid_provider_error) { ::ActiveRemote::Cached::Cache::InvalidCacheProvider }
7
+ let(:cache_provider) { ::ActiveSupport::Cache::MemoryStore.new }
8
+ let(:cache) { ::ActiveRemote::Cached::Cache.new(cache_provider) }
9
+
6
10
  describe 'API' do
7
11
  it 'validates #delete present' do
8
12
  cache = OpenStruct.new(:write => nil, :fetch => nil, :read => nil, :exist? => nil)
9
- expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(RuntimeError, /respond_to.*delete/i)
13
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*delete/i)
10
14
  end
11
15
 
12
16
  it 'validates #exist? present' do
13
17
  cache = OpenStruct.new(:write => nil, :delete => nil, :read => nil, :fetch => nil)
14
- expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(RuntimeError, /respond_to.*exist/i)
18
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*exist/i)
15
19
  end
16
20
 
17
21
  it 'validates #fetch present' do
18
22
  cache = OpenStruct.new(:write => nil, :delete => nil, :read => nil, :exist? => nil)
19
- expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(RuntimeError, /respond_to.*fetch/i)
23
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*fetch/i)
20
24
  end
21
25
 
22
26
  it 'validates #read present' do
23
27
  cache = OpenStruct.new(:write => nil, :delete => nil, :fetch => nil, :exist? => nil)
24
- expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(RuntimeError, /respond_to.*read/i)
28
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*read/i)
25
29
  end
26
30
 
27
31
  it 'validates #write present' do
28
32
  cache = OpenStruct.new(:read => nil, :delete => nil, :fetch => nil, :exist? => nil)
29
- expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(RuntimeError, /respond_to.*write/i)
33
+ expect { ::ActiveRemote::Cached.cache(cache) }.to raise_error(invalid_provider_error, /respond_to.*write/i)
34
+ end
35
+ end
36
+
37
+ describe '#nested_caching?' do
38
+ it 'is false before nested caching is enabled' do
39
+ expect(cache.nested_caching?).to eq(false)
40
+ end
41
+ end
42
+
43
+ describe 'delegation' do
44
+ it 'exposes the cache provider it was given' do
45
+ expect(cache.cache_provider).to be(cache_provider)
46
+ end
47
+
48
+ it 'delegates unknown methods to the cache provider' do
49
+ expect(cache_provider).to receive(:clear).and_return(:cleared)
50
+ expect(cache.clear).to eq(:cleared)
51
+ end
52
+ end
53
+
54
+ describe '#fetch' do
55
+ it 'returns a nil value but does not persist it by default' do
56
+ expect(cache.fetch('key') { nil }).to be_nil
57
+ expect(cache_provider.exist?('key')).to eq(false)
58
+ end
59
+
60
+ it 'returns an empty value but does not persist it by default' do
61
+ expect(cache.fetch('key') { [] }).to eq([])
62
+ expect(cache_provider.exist?('key')).to eq(false)
63
+ end
64
+
65
+ it 'persists a nil value when :allow_nil is given' do
66
+ expect(cache.fetch('key', :allow_nil => true) { nil }).to be_nil
67
+ expect(cache_provider.exist?('key')).to eq(true)
68
+ end
69
+
70
+ it 'persists an empty value when :allow_empty is given' do
71
+ expect(cache.fetch('key', :allow_empty => true) { [] }).to eq([])
72
+ expect(cache_provider.exist?('key')).to eq(true)
73
+ end
74
+
75
+ it 'persists a value that is neither nil nor empty' do
76
+ expect(cache.fetch('key') { [:record] }).to eq([:record])
77
+ expect(cache_provider.exist?('key')).to eq(true)
78
+ end
79
+ end
80
+
81
+ describe '#fetch round trips' do
82
+ let(:counting_provider) do
83
+ Class.new(::ActiveSupport::Cache::MemoryStore) do
84
+ def initialize(*args)
85
+ @calls = []
86
+ super
87
+ end
88
+
89
+ attr_reader :calls
90
+
91
+ def fetch(*args, **options, &block)
92
+ @calls << :fetch
93
+ super
94
+ end
95
+
96
+ def write(*args, **options)
97
+ @calls << :write
98
+ super
99
+ end
100
+
101
+ def delete(*args, **options)
102
+ @calls << :delete
103
+ super
104
+ end
105
+ end.new
106
+ end
107
+
108
+ # The provider used to write the nil and then take a second round trip to
109
+ # delete it. :skip_nil makes the provider skip the write.
110
+ it 'takes one provider call for a nil value' do
111
+ cache = ::ActiveRemote::Cached::Cache.new(counting_provider)
112
+
113
+ cache.fetch('key') { nil }
114
+
115
+ expect(counting_provider.calls).to eq([:fetch])
116
+ end
117
+
118
+ it 'still writes a nil when :allow_nil is given' do
119
+ cache = ::ActiveRemote::Cached::Cache.new(counting_provider)
120
+
121
+ cache.fetch('key', :allow_nil => true) { nil }
122
+
123
+ expect(counting_provider.calls).to eq(%i[fetch write])
124
+ expect(counting_provider.exist?('key')).to eq(true)
125
+ end
126
+ end
127
+
128
+ describe '#enable_nested_caching!' do
129
+ it 'writes to the cache provider only until nested caching is enabled' do
130
+ cache.write('key', 'value')
131
+
132
+ expect(cache_provider.read('key')).to eq('value')
133
+ end
134
+
135
+ context 'when nested caching is enabled' do
136
+ before do
137
+ cache.enable_nested_caching!
138
+ end
139
+
140
+ it 'writes to both the nested cache and the cache provider' do
141
+ cache.write('key', 'value')
142
+
143
+ expect(cache.read('key')).to eq('value')
144
+ expect(cache_provider.read('key')).to eq('value')
145
+ end
146
+
147
+ it 'deletes from both the nested cache and the cache provider' do
148
+ cache.write('key', 'value')
149
+ cache.delete('key')
150
+
151
+ expect(cache.read('key')).to be_nil
152
+ expect(cache_provider.read('key')).to be_nil
153
+ end
154
+
155
+ it 'reads the nested value in preference to the cache provider value' do
156
+ cache.write('key', 'nested')
157
+ cache_provider.write('key', 'provider')
158
+
159
+ expect(cache.read('key')).to eq('nested')
160
+ end
161
+
162
+ it 'reports a key that only the cache provider holds' do
163
+ cache_provider.write('key', 'provider')
164
+
165
+ expect(cache.exist?('key')).to eq(true)
166
+ end
167
+
168
+ it 'reports that nested caching is on' do
169
+ expect(cache.nested_caching?).to eq(true)
170
+ end
171
+
172
+ # A new Cache starts with nested caching off, so swapping the provider
173
+ # used to turn the setting off without saying so.
174
+ it 'keeps nested caching on when the cache provider is replaced' do
175
+ original_cache = ::ActiveRemote::Cached.cache
176
+
177
+ ::ActiveRemote::Cached.cache(cache_provider)
178
+ ::ActiveRemote::Cached.cache.enable_nested_caching!
179
+ ::ActiveRemote::Cached.cache(::ActiveSupport::Cache::MemoryStore.new)
180
+
181
+ expect(::ActiveRemote::Cached.cache.nested_caching?).to eq(true)
182
+ ensure
183
+ # .cache now carries the nested setting forward, so reset the module
184
+ # rather than call it again.
185
+ ::ActiveRemote::Cached.instance_variable_set(:@cache_provider, original_cache)
186
+ end
187
+
188
+ # #read joins the two providers with ||, so a false value in the nested
189
+ # cache falls through to the cache provider. This records that behavior.
190
+ it 'falls through to the cache provider when the nested value is false' do
191
+ cache.write('key', false)
192
+ cache_provider.write('key', 'provider')
193
+
194
+ expect(cache.read('key')).to eq('provider')
195
+ end
30
196
  end
31
197
  end
32
198
  end
@@ -5,12 +5,12 @@ require 'spec_helper'
5
5
  class DeleteMethodClass
6
6
  include ::ActiveRemote::Cached
7
7
 
8
- def self.find
9
- nil
8
+ def self.find(*)
9
+ :record
10
10
  end
11
11
 
12
- def self.search
13
- nil
12
+ def self.search(*)
13
+ [:record]
14
14
  end
15
15
 
16
16
  cached_finders_for :guid
@@ -49,4 +49,51 @@ describe DeleteMethodClass do
49
49
  expect(DeleteMethodClass).to respond_to(:cached_delete_by_client_guid_and_user_guid_and_derp)
50
50
  end
51
51
  end
52
+
53
+ describe '#cached_delete_by_guid' do
54
+ before do
55
+ ::ActiveRemote::Cached.cache(HashCache.new)
56
+ end
57
+
58
+ after do
59
+ ::ActiveRemote::Cached.default_options({})
60
+ end
61
+
62
+ it 'deletes the find cache key' do
63
+ DeleteMethodClass.cached_find_by_guid(:guid)
64
+ expect(DeleteMethodClass.cached_exist_find_by_guid?(:guid)).to eq(true)
65
+
66
+ DeleteMethodClass.cached_delete_by_guid(:guid)
67
+
68
+ expect(DeleteMethodClass.cached_exist_find_by_guid?(:guid)).to eq(false)
69
+ end
70
+
71
+ it 'deletes the search cache key' do
72
+ DeleteMethodClass.cached_search_by_guid(:guid)
73
+ expect(DeleteMethodClass.cached_exist_search_by_guid?(:guid)).to eq(true)
74
+
75
+ DeleteMethodClass.cached_delete_by_guid(:guid)
76
+
77
+ expect(DeleteMethodClass.cached_exist_search_by_guid?(:guid)).to eq(false)
78
+ end
79
+
80
+ it 'does not raise when the cache keys are not present' do
81
+ expect { DeleteMethodClass.cached_delete_by_guid(:missing) }.not_to raise_error
82
+ end
83
+
84
+ describe 'namespaced cache' do
85
+ it 'deletes the namespaced find and search cache keys' do
86
+ expect(::ActiveRemote::Cached.cache).to receive(:delete).with(
87
+ [::ActiveRemote::Cached::RUBY_AND_ACTIVE_SUPPORT_VERSION, 'MyApp', DeleteMethodClass.name, '#find',
88
+ 'guid.guid']
89
+ )
90
+ expect(::ActiveRemote::Cached.cache).to receive(:delete).with(
91
+ [::ActiveRemote::Cached::RUBY_AND_ACTIVE_SUPPORT_VERSION, 'MyApp', DeleteMethodClass.name, '#search',
92
+ 'guid.guid']
93
+ )
94
+
95
+ DeleteMethodClass.cached_delete_by_guid(:guid, :namespace => 'MyApp')
96
+ end
97
+ end
98
+ end
52
99
  end
@@ -5,12 +5,12 @@ require 'spec_helper'
5
5
  class ExistMethodClass
6
6
  include ::ActiveRemote::Cached
7
7
 
8
- def self.find
9
- nil
8
+ def self.find(*)
9
+ :record
10
10
  end
11
11
 
12
- def self.search
13
- nil
12
+ def self.search(*)
13
+ [:record]
14
14
  end
15
15
 
16
16
  cached_finders_for :guid
@@ -134,4 +134,75 @@ describe ExistMethodClass do
134
134
  expect(ExistMethodClass).to respond_to(:cached_exist_search_by_client_guid_and_user_guid_and_derp?)
135
135
  end
136
136
  end
137
+
138
+ describe '#cached_exist_find_by_guid?' do
139
+ before do
140
+ ::ActiveRemote::Cached.cache(HashCache.new)
141
+ end
142
+
143
+ after do
144
+ ::ActiveRemote::Cached.default_options({})
145
+ end
146
+
147
+ it 'returns false before the find cache key is written' do
148
+ expect(ExistMethodClass.cached_exist_find_by_guid?(:guid)).to eq(false)
149
+ end
150
+
151
+ it 'returns true after the find cache key is written' do
152
+ ExistMethodClass.cached_find_by_guid(:guid)
153
+
154
+ expect(ExistMethodClass.cached_exist_find_by_guid?(:guid)).to eq(true)
155
+ end
156
+
157
+ it 'returns the same result as the method without the question mark' do
158
+ ExistMethodClass.cached_find_by_guid(:guid)
159
+
160
+ expect(ExistMethodClass.cached_exist_find_by_guid(:guid)).to eq(
161
+ ExistMethodClass.cached_exist_find_by_guid?(:guid)
162
+ )
163
+ end
164
+
165
+ it 'ignores a search cache key for the same arguments' do
166
+ ExistMethodClass.cached_search_by_guid(:guid)
167
+
168
+ expect(ExistMethodClass.cached_exist_find_by_guid?(:guid)).to eq(false)
169
+ end
170
+
171
+ describe 'namespaced cache' do
172
+ it 'uses the namespace as a prefix to the cache key' do
173
+ expect(::ActiveRemote::Cached.cache).to receive(:exist?).with(
174
+ [::ActiveRemote::Cached::RUBY_AND_ACTIVE_SUPPORT_VERSION, 'MyApp', ExistMethodClass.name, '#find',
175
+ 'guid.guid']
176
+ ).and_return(true)
177
+
178
+ expect(ExistMethodClass.cached_exist_find_by_guid?(:guid, :namespace => 'MyApp')).to eq(true)
179
+ end
180
+ end
181
+ end
182
+
183
+ describe '#cached_exist_search_by_guid?' do
184
+ before do
185
+ ::ActiveRemote::Cached.cache(HashCache.new)
186
+ end
187
+
188
+ after do
189
+ ::ActiveRemote::Cached.default_options({})
190
+ end
191
+
192
+ it 'returns false before the search cache key is written' do
193
+ expect(ExistMethodClass.cached_exist_search_by_guid?(:guid)).to eq(false)
194
+ end
195
+
196
+ it 'returns true after the search cache key is written' do
197
+ ExistMethodClass.cached_search_by_guid(:guid)
198
+
199
+ expect(ExistMethodClass.cached_exist_search_by_guid?(:guid)).to eq(true)
200
+ end
201
+
202
+ it 'ignores a find cache key for the same arguments' do
203
+ ExistMethodClass.cached_find_by_guid(:guid)
204
+
205
+ expect(ExistMethodClass.cached_exist_search_by_guid?(:guid)).to eq(false)
206
+ end
207
+ end
137
208
  end
@@ -21,9 +21,7 @@ class FindMethodClass
21
21
  end
22
22
 
23
23
  describe FindMethodClass do
24
- let(:versioned_prefix) do
25
- "#{RUBY_ENGINE_VERSION}:#{ActiveSupport::VERSION::STRING}"
26
- end
24
+ let(:versioned_prefix) { ::ActiveRemote::Cached::RUBY_AND_ACTIVE_SUPPORT_VERSION }
27
25
 
28
26
  describe 'API' do
29
27
  it "creates 'cached_find_by_foo'" do
@@ -81,7 +79,7 @@ describe FindMethodClass do
81
79
 
82
80
  it 'merges the default options in for the fetch call' do
83
81
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
84
- [versioned_prefix, FindMethodClass.name, '#find', 'guid'], { :expires_in => 100 }
82
+ [versioned_prefix, FindMethodClass.name, '#find', 'guid.guid'], { :expires_in => 100 }
85
83
  ).and_return(:hello)
86
84
  expect(FindMethodClass).not_to receive(:find)
87
85
  expect(FindMethodClass.cached_find_by_guid(:guid)).to eq(:hello)
@@ -89,7 +87,7 @@ describe FindMethodClass do
89
87
 
90
88
  it 'overrides the default options with local options for the fetch call' do
91
89
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
92
- [versioned_prefix, FindMethodClass.name, '#find', 'guid'], { :expires_in => 200 }
90
+ [versioned_prefix, FindMethodClass.name, '#find', 'guid.guid'], { :expires_in => 200 }
93
91
  ).and_return(:hello)
94
92
  expect(FindMethodClass).not_to receive(:find)
95
93
 
@@ -103,7 +101,7 @@ describe FindMethodClass do
103
101
 
104
102
  it 'uses the namespace as a prefix to the cache key' do
105
103
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
106
- [versioned_prefix, 'MyApp', FindMethodClass.name, '#find', 'guid'], { :expires_in => 100 }
104
+ [versioned_prefix, 'MyApp', FindMethodClass.name, '#find', 'guid.guid'], { :expires_in => 100 }
107
105
  ).and_return(:hello)
108
106
 
109
107
  expect(FindMethodClass).not_to receive(:find)
@@ -124,7 +122,7 @@ describe FindMethodClass do
124
122
 
125
123
  it 'overrides the default options with cached_finder options for the fetch call' do
126
124
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
127
- [versioned_prefix, FindMethodClass.name, '#find', 'foo'], { :expires_in => 500 }
125
+ [versioned_prefix, FindMethodClass.name, '#find', 'foo.foo'], { :expires_in => 500 }
128
126
  ).and_return(:hello)
129
127
 
130
128
  expect(FindMethodClass).not_to receive(:find)
@@ -133,7 +131,7 @@ describe FindMethodClass do
133
131
 
134
132
  it 'overrides the cached_finder options with local options for the fetch call' do
135
133
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
136
- [versioned_prefix, FindMethodClass.name, '#find', 'foo'], { :expires_in => 200 }
134
+ [versioned_prefix, FindMethodClass.name, '#find', 'foo.foo'], { :expires_in => 200 }
137
135
  ).and_return(:hello)
138
136
 
139
137
  expect(FindMethodClass).not_to receive(:find)
@@ -26,9 +26,7 @@ class SearchMethodClass
26
26
  end
27
27
 
28
28
  describe SearchMethodClass do
29
- let(:versioned_prefix) do
30
- "#{RUBY_ENGINE_VERSION}:#{ActiveSupport::VERSION::STRING}"
31
- end
29
+ let(:versioned_prefix) { ::ActiveRemote::Cached::RUBY_AND_ACTIVE_SUPPORT_VERSION }
32
30
 
33
31
  describe 'API' do
34
32
  it "creates 'cached_search_by_foo'" do
@@ -166,7 +164,7 @@ describe SearchMethodClass do
166
164
 
167
165
  it 'merges the default options in for the fetch call' do
168
166
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
169
- [versioned_prefix, SearchMethodClass.name, '#search', 'guid'], { :expires_in => 100 }
167
+ [versioned_prefix, SearchMethodClass.name, '#search', 'guid.guid'], { :expires_in => 100 }
170
168
  ).and_return(:hello)
171
169
 
172
170
  expect(SearchMethodClass).not_to receive(:search)
@@ -175,7 +173,7 @@ describe SearchMethodClass do
175
173
 
176
174
  it 'overrides the default options with local options for the fetch call' do
177
175
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
178
- [versioned_prefix, SearchMethodClass.name, '#search', 'guid'], { :expires_in => 200 }
176
+ [versioned_prefix, SearchMethodClass.name, '#search', 'guid.guid'], { :expires_in => 200 }
179
177
  ).and_return(:hello)
180
178
 
181
179
  expect(SearchMethodClass).not_to receive(:search)
@@ -189,7 +187,7 @@ describe SearchMethodClass do
189
187
 
190
188
  it 'uses the namespace as a prefix to the cache key' do
191
189
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
192
- [versioned_prefix, 'MyApp', SearchMethodClass.name, '#search', 'guid'], { :expires_in => 100 }
190
+ [versioned_prefix, 'MyApp', SearchMethodClass.name, '#search', 'guid.guid'], { :expires_in => 100 }
193
191
  ).and_return(:hello)
194
192
 
195
193
  expect(SearchMethodClass).not_to receive(:search)
@@ -210,7 +208,7 @@ describe SearchMethodClass do
210
208
 
211
209
  it 'overrides the default options with cached_finder options for the fetch call' do
212
210
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
213
- [versioned_prefix, SearchMethodClass.name, '#search', 'foo'], { :expires_in => 500 }
211
+ [versioned_prefix, SearchMethodClass.name, '#search', 'foo.foo'], { :expires_in => 500 }
214
212
  ).and_return(:hello)
215
213
 
216
214
  expect(SearchMethodClass).not_to receive(:find)
@@ -219,7 +217,7 @@ describe SearchMethodClass do
219
217
 
220
218
  it 'overrides the cached_finder options with local options for the fetch call' do
221
219
  expect(::ActiveRemote::Cached.cache).to receive(:fetch).with(
222
- [versioned_prefix, SearchMethodClass.name, '#search', 'foo'], { :expires_in => 200 }
220
+ [versioned_prefix, SearchMethodClass.name, '#search', 'foo.foo'], { :expires_in => 200 }
223
221
  ).and_return(:hello)
224
222
 
225
223
  expect(SearchMethodClass).not_to receive(:find)
@@ -248,6 +246,57 @@ describe SearchMethodClass do
248
246
  SearchMethodClass.cached_search_by_foo!(:foo, :expires_in => 200)
249
247
  end.to raise_error ::ActiveRemote::RemoteRecordNotFound
250
248
  end
249
+
250
+ it 'raises ActiveRemote::RemoteRecordNotFound when the block returns no results' do
251
+ expect(SearchMethodClass).not_to receive(:search)
252
+ expect do
253
+ SearchMethodClass.cached_search_by_foo!(:foo) { [] }
254
+ end.to raise_error ::ActiveRemote::RemoteRecordNotFound
255
+ end
256
+
257
+ it 'raises ActiveRemote::RemoteRecordNotFound when the first result is nil' do
258
+ expect(SearchMethodClass).not_to receive(:search)
259
+ expect do
260
+ SearchMethodClass.cached_search_by_foo!(:foo) { [nil] }
261
+ end.to raise_error ::ActiveRemote::RemoteRecordNotFound
262
+ end
263
+
264
+ it 'raises ActiveRemote::RemoteRecordNotFound when the results are nil' do
265
+ expect(SearchMethodClass).to receive(:search).and_return(nil)
266
+ expect do
267
+ SearchMethodClass.cached_search_by_foo!(:foo)
268
+ end.to raise_error ::ActiveRemote::RemoteRecordNotFound
269
+ end
270
+
271
+ it 'names the model class in the error' do
272
+ expect(SearchMethodClass).to receive(:search).and_return([])
273
+ expect do
274
+ SearchMethodClass.cached_search_by_foo!(:foo)
275
+ end.to raise_error(::ActiveRemote::RemoteRecordNotFound, /SearchMethodClass/)
276
+ end
277
+
278
+ it 'reports the model class on the error' do
279
+ expect(SearchMethodClass).to receive(:search).and_return([])
280
+
281
+ SearchMethodClass.cached_search_by_foo!(:foo)
282
+ rescue ::ActiveRemote::RemoteRecordNotFound => e
283
+ expect(e.remote_record_class).to eq(SearchMethodClass)
284
+ end
285
+
286
+ it 'does not cache the results when it raises' do
287
+ expect do
288
+ SearchMethodClass.cached_search_by_foo!(:foo) { [] }
289
+ end.to raise_error ::ActiveRemote::RemoteRecordNotFound
290
+
291
+ expect(SearchMethodClass.cached_exist_search_by_foo?(:foo)).to eq(false)
292
+ end
293
+
294
+ it 'caches the results when they are present' do
295
+ expect(SearchMethodClass).to receive(:search).once.and_return([:hello])
296
+
297
+ expect(SearchMethodClass.cached_search_by_foo!(:foo)).to eq([:hello])
298
+ expect(SearchMethodClass.cached_search_by_foo!(:foo)).to eq([:hello])
299
+ end
251
300
  end
252
301
 
253
302
  # This tests the method_missing override