bblimke / webmock

Library for stubbing and setting expectations on HTTP requests in Ruby.
MIT License
3.97k stars 555 forks source link

Webmock matchers and forked processes #1062

Open kwstannard opened 4 months ago

kwstannard commented 4 months ago

I think this is maybe a feature request. WebMock matchers do not work with forking. This makes sense logically given that a web request will change the stub object and trigger copy-on-write. It would be great if there was a way to have the copied stub in the fork send back the recorded requests to the main process stub though.

Ruby 3.3.3 WebMock 3.23.1

require 'webmock'
require 'webmock/rspec'

def fork_code
  3.times {
    fork do
      Net::HTTP.get(URI.parse('http://localhost'))
    end
  }
  Process.waitall
end

def thread_code
  3.times {
    Thread.new do
      Net::HTTP.get(URI.parse('http://localhost'))
    end
  }
  Process.waitall
end

# test
WebMock.enable!
WebMock.disable_net_connect!
stub = WebMock.stub_request(:get, 'localhost').and_return(body: ->(_) { printf 'Called! '; ''} )

fork_code

matcher = WebMock::RequestPatternMatcher.new.times(3)
puts "Does the main process count forked requests? #{matcher.matches?(stub)}"
puts
puts matcher.failure_message

thread_code

matcher = WebMock::RequestPatternMatcher.new.times(3)
puts "Does the main process count threaded requests? #{matcher.matches?(stub)}"
puts
puts matcher.failure_message
~ $ ruby /tmp/fork_spec.rb
Called! Called! Called! Does the main process count forked requests? false

The request GET http://localhost/ was expected to execute 3 times but it executed 0 times

The following requests were made:

No requests were made.
============================================================
Called! Called! Called! Does the main process count threaded requests? true

The request GET http://localhost/ was expected to execute 3 times but it executed 3 times

The following requests were made:

GET http://localhost/ with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Host'=>'localhost', 'User-Agent'=>'Ruby'} was made 3 times

============================================================
bblimke commented 4 months ago

@kwstannard I understand the issue and what you're trying to achieve. However, this isn't something WebMock is designed to handle. WebMock uses global registries in the current process's memory and doesn't differentiate between main and subprocesses. The behaviour you're seeing is correct given how forking works.

Here are some ideas on how you could solve that:

  1. Using Inter-Process Communication (IPC) to send WebMock registry data from subprocesses to the main process.
  2. Implementing a shared storage solution (like a database or file) that all processes can access and overwrite WebMock registries to use that storage instead of the default memory storage.
  3. Creating some kind of wrapper around WebMock that's aware of forking, though I'm not sure how this would work.
kwstannard commented 4 months ago

Thanks bblimke.