webmock 1.7.10 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.travis.yml +2 -2
  2. data/CHANGELOG.md +98 -24
  3. data/Gemfile +2 -3
  4. data/README.md +45 -4
  5. data/Rakefile +2 -2
  6. data/lib/webmock.rb +3 -0
  7. data/lib/webmock/api.rb +34 -6
  8. data/lib/webmock/http_lib_adapters/curb_adapter.rb +4 -41
  9. data/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb +1 -1
  10. data/lib/webmock/http_lib_adapters/excon_adapter.rb +94 -0
  11. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +31 -4
  12. data/lib/webmock/http_lib_adapters/net_http.rb +2 -0
  13. data/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +4 -3
  14. data/lib/webmock/matchers/hash_including_matcher.rb +25 -0
  15. data/lib/webmock/rack_response.rb +8 -1
  16. data/lib/webmock/request_pattern.rb +108 -77
  17. data/lib/webmock/request_signature.rb +1 -0
  18. data/lib/webmock/stub_registry.rb +9 -8
  19. data/lib/webmock/version.rb +1 -1
  20. data/lib/webmock/webmock.rb +5 -2
  21. data/minitest/webmock_spec.rb +22 -2
  22. data/spec/acceptance/curb/curb_spec_helper.rb +12 -2
  23. data/spec/acceptance/em_http_request/em_http_request_spec.rb +42 -33
  24. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +4 -2
  25. data/spec/acceptance/excon/excon_spec.rb +15 -0
  26. data/spec/acceptance/excon/excon_spec_helper.rb +37 -0
  27. data/spec/acceptance/net_http/net_http_spec.rb +7 -0
  28. data/spec/acceptance/net_http/net_http_spec_helper.rb +3 -1
  29. data/spec/acceptance/patron/patron_spec.rb +12 -3
  30. data/spec/acceptance/patron/patron_spec_helper.rb +2 -2
  31. data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +3 -3
  32. data/spec/acceptance/shared/callbacks.rb +22 -6
  33. data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +21 -0
  34. data/spec/acceptance/shared/enabling_and_disabling_webmock.rb +10 -11
  35. data/spec/acceptance/shared/precedence_of_stubs.rb +1 -1
  36. data/spec/acceptance/shared/request_expectations.rb +49 -3
  37. data/spec/acceptance/shared/returning_declared_responses.rb +9 -21
  38. data/spec/acceptance/shared/stubbing_requests.rb +80 -4
  39. data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +1 -1
  40. data/spec/acceptance/webmock_shared.rb +11 -8
  41. data/spec/spec_helper.rb +3 -3
  42. data/spec/support/my_rack_app.rb +25 -1
  43. data/spec/support/webmock_server.rb +9 -6
  44. data/spec/unit/rack_response_spec.rb +18 -0
  45. data/spec/unit/request_pattern_spec.rb +205 -96
  46. data/spec/unit/request_signature_spec.rb +36 -34
  47. data/spec/unit/util/uri_spec.rb +14 -2
  48. data/test/shared_test.rb +31 -2
  49. data/webmock.gemspec +9 -7
  50. metadata +86 -73
@@ -0,0 +1,21 @@
1
+ shared_context "complex cross-concern behaviors" do |*adapter_info|
2
+ it 'allows a response with multiple values for the same header to be recorded and played back exactly as-is' do
3
+ WebMock.allow_net_connect!
4
+
5
+ recorded_response = nil
6
+ WebMock.after_request { |_,r| recorded_response = r }
7
+ real_response = http_request(:get, webmock_server_url)
8
+
9
+ stub_request(:get, webmock_server_url).to_return(
10
+ :status => recorded_response.status,
11
+ :body => recorded_response.body,
12
+ :headers => recorded_response.headers
13
+ )
14
+
15
+ played_back_response = http_request(:get, webmock_server_url)
16
+
17
+ played_back_response.headers.keys.should include('Set-Cookie')
18
+ played_back_response.should == real_response
19
+ end
20
+ end
21
+
@@ -1,4 +1,4 @@
1
- shared_context "enabled and disabled webmock" do
1
+ shared_context "enabled and disabled webmock" do |*adapter_info|
2
2
  describe "when webmock is disabled" do
3
3
  before(:each) do
4
4
  WebMock.disable!
@@ -41,28 +41,27 @@ end
41
41
 
42
42
  shared_context "disabled WebMock" do
43
43
  it "should not register executed requests" do
44
- http_request(:get, "http://www.example.com/")
45
- a_request(:get, "http://www.example.com/").should_not have_been_made
44
+ http_request(:get, webmock_server_url)
45
+ a_request(:get, webmock_server_url).should_not have_been_made
46
46
  end
47
47
 
48
48
  it "should not block unstubbed requests" do
49
49
  lambda {
50
- http_request(:get, "http://www.example.com/")
50
+ http_request(:get, webmock_server_url)
51
51
  }.should_not raise_error
52
52
  end
53
53
 
54
54
  it "should return real response even if there are stubs" do
55
55
  stub_request(:get, /.*/).to_return(:body => "x")
56
- http_request(:get, "http://www.example.com/").
57
- status.should == "302"
56
+ http_request(:get, webmock_server_url).body.should == "hello world"
58
57
  end
59
58
 
60
59
  it "should not invoke any callbacks" do
61
60
  WebMock.reset_callbacks
62
- stub_request(:get, "http://www.example.com/")
61
+ stub_request(:get, webmock_server_url)
63
62
  @called = nil
64
63
  WebMock.after_request { @called = 1 }
65
- http_request(:get, "http://www.example.com/")
64
+ http_request(:get, webmock_server_url)
66
65
  @called.should == nil
67
66
  end
68
67
  end
@@ -70,8 +69,8 @@ end
70
69
  shared_context "enabled WebMock" do
71
70
  it "should register executed requests" do
72
71
  WebMock.allow_net_connect!
73
- http_request(:get, "http://www.example.com/")
74
- a_request(:get, "http://www.example.com/").should have_been_made
72
+ http_request(:get, webmock_server_url)
73
+ a_request(:get, webmock_server_url).should have_been_made
75
74
  end
76
75
 
77
76
  it "should block unstubbed requests" do
@@ -90,7 +89,7 @@ shared_context "enabled WebMock" do
90
89
  WebMock.reset_callbacks
91
90
  @called = nil
92
91
  WebMock.after_request { @called = 1 }
93
- http_request(:get, "http://www.example.com/")
92
+ http_request(:get, webmock_server_url)
94
93
  @called.should == 1
95
94
  end
96
95
  end
@@ -1,4 +1,4 @@
1
- shared_context "precedence of stubs" do
1
+ shared_context "precedence of stubs" do |*adapter_info|
2
2
  describe "when choosing a matching request stub" do
3
3
  it "should use the last declared matching request stub" do
4
4
  stub_request(:get, "www.example.com").to_return(:body => "abc")
@@ -1,4 +1,4 @@
1
- shared_context "request expectations" do
1
+ shared_context "request expectations" do |*adapter_info|
2
2
  describe "when request expectations are set" do
3
3
  describe "when net connect is not allowed" do
4
4
  before(:each) do
@@ -150,6 +150,13 @@ shared_context "request expectations" do
150
150
  a_request(:get, "www.example.com/?x=3").with(:query => {"a" => ["b", "c"]}).should have_been_made
151
151
  }.should_not raise_error
152
152
  end
153
+
154
+ it "should satisfy expectation if the request was executed with only part query params declared as a hash in a query option" do
155
+ lambda {
156
+ http_request(:get, "http://www.example.com/?a[]=b&a[]=c&b=1")
157
+ a_request(:get, "www.example.com").with(:query => hash_including({"a" => ["b", "c"]})).should have_been_made
158
+ }.should_not raise_error
159
+ end
153
160
  end
154
161
 
155
162
  it "should fail if request was made more times than expected" do
@@ -209,7 +216,7 @@ shared_context "request expectations" do
209
216
 
210
217
  describe "when expected reqest body is declared as a hash" do
211
218
  let(:body_hash) { {:a => '1', :b => 'five', 'c' => {'d' => ['e', 'f']}} }
212
- let(:fail_message) {%r(The request POST http://www.example.com/ with body \{"a"=>"1", "b"=>"five", "c"=>\{"d"=>\["e", "f"\]\}\} was expected to execute 1 time but it executed 0 times)}
219
+ let(:fail_message) {%r(The request POST http://www.example.com/ with body .+ was expected to execute 1 time but it executed 0 times)}
213
220
 
214
221
  describe "when request is made with url encoded body matching hash" do
215
222
  it "should satisfy expectation" do
@@ -290,6 +297,26 @@ shared_context "request expectations" do
290
297
  end
291
298
  end
292
299
 
300
+ describe "when expected reqest body is declared as a partial hash matcher" do
301
+ let(:body_hash) { hash_including({:a => '1', 'c' => {'d' => ['e', 'f']}}) }
302
+ let(:fail_message) {%r(The request POST http://www.example.com/ with body hash_including(.+) was expected to execute 1 time but it executed 0 times)}
303
+
304
+ describe "when request is made with url encoded body matching hash" do
305
+ it "should satisfy expectation" do
306
+ lambda {
307
+ http_request(:post, "http://www.example.com/", :body => 'a=1&c[d][]=e&c[d][]=f&b=five')
308
+ a_request(:post, "www.example.com").with(:body => body_hash).should have_been_made
309
+ }.should_not raise_error
310
+ end
311
+
312
+ it "should fail if request is executed with url encoded body not matching hash" do
313
+ lambda {
314
+ http_request(:post, "http://www.example.com/", :body => 'c[d][]=f&a=1&c[d][]=e')
315
+ a_request(:post, "www.example.com").with(:body => body_hash).should have_been_made
316
+ }.should fail_with(fail_message)
317
+ end
318
+ end
319
+ end
293
320
 
294
321
  describe "when request with headers is expected" do
295
322
  it "should satisfy expectation if request was executed with the same headers" do
@@ -410,7 +437,7 @@ shared_context "request expectations" do
410
437
  end
411
438
  end
412
439
 
413
- describe "with authentication" do
440
+ describe "with authentication", :unless => (adapter_info.include?(:no_url_auth)) do
414
441
  before(:each) do
415
442
  stub_request(:any, "http://user:pass@www.example.com")
416
443
  stub_request(:any, "http://user:pazz@www.example.com")
@@ -541,6 +568,25 @@ shared_context "request expectations" do
541
568
  }.should fail_with(%r(The request POST http://www.example.com/ with given block was expected to execute 1 time but it executed 0 times))
542
569
  end
543
570
  end
571
+
572
+ describe "when expectation is declared using assert_requested" do
573
+ it "should satisfy expectation if requests was made" do
574
+ stub_http = stub_http_request(:get, "http://www.example.com")
575
+ lambda {
576
+ http_request(:get, "http://www.example.com/")
577
+ assert_requested(stub_http, :times => 1)
578
+ assert_requested(stub_http)
579
+ }.should_not raise_error
580
+ end
581
+
582
+ it "should fail if request expected not to be made was not wade" do
583
+ stub_http = stub_http_request(:get, "http://www.example.com")
584
+ lambda {
585
+ http_request(:get, "http://www.example.com/")
586
+ assert_not_requested(stub_http)
587
+ }.should fail_with(%r(The request GET http://www.example.com/ was expected to execute 0 times but it executed 1 time))
588
+ end
589
+ end
544
590
  end
545
591
 
546
592
 
@@ -1,6 +1,6 @@
1
1
  class MyException < StandardError; end;
2
2
 
3
- shared_context "declared responses" do
3
+ shared_context "declared responses" do |*adapter_info|
4
4
  describe "when request stub declares that request should raise exception" do
5
5
  it "should raise exception" do
6
6
  stub_request(:get, "www.example.com").to_raise(MyException)
@@ -72,13 +72,10 @@ shared_context "declared responses" do
72
72
  http_request(:get, "http://www.example.com/").status.should == "500"
73
73
  end
74
74
 
75
- it "should return response with declared status message" do
75
+ it "should return response with declared status message", :unless => (adapter_info.include?(:no_status_message)) do
76
76
  stub_request(:get, "www.example.com").to_return(:status => [500, "Internal Server Error"])
77
77
  response = http_request(:get, "http://www.example.com/")
78
- # not supported by em-http-request, it always returns "unknown" for http_reason
79
- unless http_library == :em_http_request
80
- response.message.should == "Internal Server Error"
81
- end
78
+ response.message.should == "Internal Server Error"
82
79
  end
83
80
 
84
81
  it "should return response with a default status code" do
@@ -86,13 +83,10 @@ shared_context "declared responses" do
86
83
  http_request(:get, "http://www.example.com/").status.should == "200"
87
84
  end
88
85
 
89
- it "should return default response with empty message if response was not declared" do
86
+ it "should return default response with empty message if response was not declared", :unless => (adapter_info.include?(:no_status_message)) do
90
87
  stub_request(:get, "www.example.com")
91
88
  response = http_request(:get, "http://www.example.com/")
92
- # not supported by em-http-request, it always returns "unknown" for http_reason
93
- unless http_library == :em_http_request
94
- response.message.should == ""
95
- end
89
+ response.message.should == ""
96
90
  end
97
91
 
98
92
  describe "when response body was declared as IO" do
@@ -186,11 +180,8 @@ shared_context "declared responses" do
186
180
  @response.status.should == "202"
187
181
  end
188
182
 
189
- it "should return recorded status message" do
190
- # not supported by em-http-request, it always returns "unknown" for http_reason
191
- unless http_library == :em_http_request
192
- @response.message.should == "OK"
193
- end
183
+ it "should return recorded status message", :unless => (adapter_info.include?(:no_status_message)) do
184
+ @response.message.should == "OK"
194
185
  end
195
186
 
196
187
  it "should ensure file is closed" do
@@ -223,11 +214,8 @@ shared_context "declared responses" do
223
214
  @response.status.should == "202"
224
215
  end
225
216
 
226
- it "should return recorded status message" do
227
- # not supported by em-http-request, it always returns "unknown" for http_reason
228
- unless http_library == :em_http_request
229
- @response.message.should == "OK"
230
- end
217
+ it "should return recorded status message", :unless => (adapter_info.include?(:no_status_message)) do
218
+ @response.message.should == "OK"
231
219
  end
232
220
  end
233
221
 
@@ -1,4 +1,4 @@
1
- shared_examples_for "stubbing requests" do
1
+ shared_examples_for "stubbing requests" do |*adapter_info|
2
2
  describe "when requests are stubbed" do
3
3
  describe "based on uri" do
4
4
  it "should return stubbed response even if request have escaped parameters" do
@@ -32,6 +32,11 @@ shared_examples_for "stubbing requests" do
32
32
  stub_request(:get, "www.example.com/?x=3").with(:query => {"a" => ["b", "c"]}).to_return(:body => "abc")
33
33
  http_request(:get, "http://www.example.com/?x=3&a[]=b&a[]=c").body.should == "abc"
34
34
  end
35
+
36
+ it "should return stubbed response when stub expects only part of query params" do
37
+ stub_request(:get, "www.example.com").with(:query => hash_including({"a" => ["b", "c"]})).to_return(:body => "abc")
38
+ http_request(:get, "http://www.example.com/?a[]=b&a[]=c&b=1").body.should == "abc"
39
+ end
35
40
  end
36
41
 
37
42
  describe "based on method" do
@@ -111,7 +116,7 @@ shared_examples_for "stubbing requests" do
111
116
  lambda {
112
117
  http_request(
113
118
  :post, "http://www.example.com/",
114
- :body => 'c[d][]=f&a=1&c[d][]=e').status.should == "200"
119
+ :body => 'c[d][]=f&a=1&c[d][]=e')
115
120
  }.should raise_error(WebMock::NetConnectNotAllowedError, %r(Real HTTP connections are disabled. Unregistered request: POST http://www.example.com/ with body 'c\[d\]\[\]=f&a=1&c\[d\]\[\]=e'))
116
121
  end
117
122
  end
@@ -169,6 +174,37 @@ shared_examples_for "stubbing requests" do
169
174
  end
170
175
  end
171
176
  end
177
+
178
+ describe "when body is declared as partial hash matcher" do
179
+ before(:each) do
180
+ stub_request(:post, "www.example.com").
181
+ with(:body => hash_including({:a => '1', 'c' => {'d' => ['e', 'f']} }))
182
+ end
183
+
184
+ describe "for request with url encoded body" do
185
+ it "should match request if hash matches body" do
186
+ http_request(
187
+ :post, "http://www.example.com/",
188
+ :body => 'a=1&c[d][]=e&c[d][]=f&b=five').status.should == "200"
189
+ end
190
+
191
+ it "should not match if hash doesn't match url encoded body" do
192
+ lambda {
193
+ http_request(
194
+ :post, "http://www.example.com/",
195
+ :body => 'c[d][]=f&a=1&c[d][]=e').status
196
+ }.should raise_error
197
+ end
198
+ end
199
+
200
+ describe "for request with json body and content type is set to json" do
201
+ it "should match if hash matches body" do
202
+ http_request(
203
+ :post, "http://www.example.com/", :headers => {'Content-Type' => 'application/json'},
204
+ :body => "{\"a\":\"1\",\"c\":{\"d\":[\"e\",\"f\"]},\"b\":\"five\"}").status.should == "200"
205
+ end
206
+ end
207
+ end
172
208
  end
173
209
 
174
210
  describe "based on headers" do
@@ -259,7 +295,7 @@ shared_examples_for "stubbing requests" do
259
295
  end
260
296
  end
261
297
 
262
- describe "when stubbing request with basic authentication" do
298
+ describe "when stubbing request with basic authentication", :unless => (adapter_info.include?(:no_url_auth)) do
263
299
  it "should match if credentials are the same" do
264
300
  stub_request(:get, "user:pass@www.example.com")
265
301
  http_request(:get, "http://user:pass@www.example.com/").status.should == "200"
@@ -289,7 +325,7 @@ shared_examples_for "stubbing requests" do
289
325
 
290
326
  describe "when stubbing request with a global hook" do
291
327
  after(:each) do
292
- WebMock::StubRegistry.instance.global_stub = nil
328
+ WebMock::StubRegistry.instance.global_stubs.clear
293
329
  end
294
330
 
295
331
  it 'returns the response returned by the hook' do
@@ -335,6 +371,46 @@ shared_examples_for "stubbing requests" do
335
371
  http_request(:get, "http://www.example.com/")
336
372
  call_count.should == 1
337
373
  end
374
+
375
+ it 'supports multiple global stubs; the first registered one that returns a non-nil value determines the stub' do
376
+ stub_invocation_order = []
377
+ WebMock.globally_stub_request do |request|
378
+ stub_invocation_order << :nil_stub
379
+ nil
380
+ end
381
+
382
+ WebMock.globally_stub_request do |request|
383
+ stub_invocation_order << :hash_stub
384
+ { :body => "global stub body" }
385
+ end
386
+
387
+ http_request(:get, "http://www.example.com/").body.should == "global stub body"
388
+ stub_invocation_order.should eq([:nil_stub, :hash_stub])
389
+ end
390
+
391
+ [:before, :after].each do |before_or_after|
392
+ context "when there is also a non-global registered stub #{before_or_after} the global stub" do
393
+ def stub_non_globally
394
+ stub_request(:get, "www.example.com").to_return(:body => 'non-global stub body')
395
+ end
396
+
397
+ define_method :register_stubs do |block|
398
+ stub_non_globally if before_or_after == :before
399
+ WebMock.globally_stub_request(&block)
400
+ stub_non_globally if before_or_after == :after
401
+ end
402
+
403
+ it 'uses the response from the global stub if the block returns a non-nil value' do
404
+ register_stubs(lambda { |req| { :body => 'global stub body' } })
405
+ http_request(:get, "http://www.example.com/").body.should == "global stub body"
406
+ end
407
+
408
+ it 'uses the response from the non-global stub if the block returns a nil value' do
409
+ register_stubs(lambda { |req| nil })
410
+ http_request(:get, "http://www.example.com/").body.should == "non-global stub body"
411
+ end
412
+ end
413
+ end
338
414
  end
339
415
 
340
416
  describe "when stubbing request with a block evaluated on request" do
@@ -11,7 +11,7 @@ module TyphoeusHydraSpecHelper
11
11
  :method => method,
12
12
  :body => options[:body],
13
13
  :headers => options[:headers],
14
- :timeout => 15000 # milliseconds
14
+ :timeout => 25000 # milliseconds
15
15
  }
16
16
  )
17
17
  raise FakeTyphoeusHydraError.new if response.code.to_s == "0"
@@ -6,6 +6,7 @@ require 'acceptance/shared/request_expectations'
6
6
  require 'acceptance/shared/stubbing_requests'
7
7
  require 'acceptance/shared/allowing_and_disabling_net_connect'
8
8
  require 'acceptance/shared/precedence_of_stubs'
9
+ require 'acceptance/shared/complex_cross_concern_behaviors'
9
10
 
10
11
  unless defined? SAMPLE_HEADERS
11
12
  SAMPLE_HEADERS = { "Content-Length" => "8888", "Accept" => "application/json" }
@@ -13,7 +14,7 @@ unless defined? SAMPLE_HEADERS
13
14
  NOT_ESCAPED_PARAMS = "z='Stop!' said Fred&x=ab c"
14
15
  end
15
16
 
16
- shared_examples "with WebMock" do
17
+ shared_examples "with WebMock" do |*adapter_info|
17
18
  describe "with WebMock" do
18
19
  let(:webmock_server_url) {"http://#{WebMockServer.instance.host_with_port}/"}
19
20
  before(:each) do
@@ -21,18 +22,20 @@ shared_examples "with WebMock" do
21
22
  WebMock.reset!
22
23
  end
23
24
 
24
- include_context "allowing and disabling net connect"
25
+ include_context "allowing and disabling net connect", *adapter_info
25
26
 
26
- include_context "stubbing requests"
27
+ include_context "stubbing requests", *adapter_info
27
28
 
28
- include_context "declared responses"
29
+ include_context "declared responses", *adapter_info
29
30
 
30
- include_context "precedence of stubs"
31
+ include_context "precedence of stubs", *adapter_info
31
32
 
32
- include_context "request expectations"
33
+ include_context "request expectations", *adapter_info
33
34
 
34
- include_context "callbacks"
35
+ include_context "callbacks", *adapter_info
35
36
 
36
- include_context "enabled and disabled webmock"
37
+ include_context "enabled and disabled webmock", *adapter_info
38
+
39
+ include_context "complex cross-concern behaviors", *adapter_info
37
40
  end
38
41
  end
@@ -30,11 +30,11 @@ RSpec.configure do |config|
30
30
 
31
31
  config.filter_run_excluding :without_webmock => true
32
32
 
33
- config.before(:all) do
34
- WebMockServer.instance.start
33
+ config.before(:suite) do
34
+ WebMockServer.instance.start unless WebMockServer.instance.started
35
35
  end
36
36
 
37
- config.after(:all) do
37
+ config.after(:suite) do
38
38
  WebMockServer.instance.stop
39
39
  end
40
40
 
@@ -1,6 +1,17 @@
1
1
  require 'rack'
2
2
 
3
3
  class MyRackApp
4
+ class NonArrayResponse
5
+ # The rack response body need not implement #join,
6
+ # but it must implement #each. It need not be an Array.
7
+ # ActionDispatch::Response, for example, exercises that fact.
8
+ # See: http://rack.rubyforge.org/doc/SPEC.html
9
+
10
+ def each(*args, &blk)
11
+ ["This is not in an array!"].each(*args, &blk)
12
+ end
13
+ end
14
+
4
15
  def self.call(env)
5
16
  case env.values_at('REQUEST_METHOD', 'PATH_INFO')
6
17
  when ['GET', '/']
@@ -8,6 +19,10 @@ class MyRackApp
8
19
  when ['GET', '/greet']
9
20
  name = env['QUERY_STRING'][/name=([^&]*)/, 1] || "World"
10
21
  [200, {}, ["Hello, #{name}"]]
22
+ when ['GET', '/non_array_response']
23
+ [200, {}, NonArrayResponse.new]
24
+ when ['GET', '/locked']
25
+ [200, {}, ["Single threaded response."]]
11
26
  when ['POST', '/greet']
12
27
  name = env["rack.input"].read[/name=([^&]*)/, 1] || "World"
13
28
  [200, {}, ["Good to meet you, #{name}!"]]
@@ -15,4 +30,13 @@ class MyRackApp
15
30
  [404, {}, ['']]
16
31
  end
17
32
  end
18
- end
33
+ end
34
+
35
+ class MyLockedRackApp
36
+ MUTEX = Mutex.new
37
+
38
+ def self.call(env)
39
+ lock = Rack::Lock.new(MyRackApp, MUTEX)
40
+ lock.call(env)
41
+ end
42
+ end