Module: RSpec::StubbedEnv::StubHelpers

Included in:
HideHelpers
Defined in:
lib/rspec/stubbed_env/stub_helpers.rb

Overview

Helpers to unobtrusively stub ENV

Instance Method Summary collapse

Instance Method Details

#env_stubbed? ⇒ Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/rspec/stubbed_env/stub_helpers.rb', line 58

def env_stubbed?
  instance_variable_defined?(:@__rspec_stubbed_env_mutations) && !@__rspec_stubbed_env_mutations.empty?
end

#init_stub ⇒ Object



62
63
64
65
66
# File 'lib/rspec/stubbed_env/stub_helpers.rb', line 62

def init_stub
  @__rspec_stubbed_env_mutations ||= []
  install_env_accessors
  add_stubbed_value(STUBBED_KEY, true, :track => false)
end

#install_env_accessors ⇒ Object

Install ONE block-backed receive per accessor instead of per-key
.with(key) expectations. Per-key expectations collide when the same
key is stubbed again in a later example after the earlier expectation
has been invoked — rspec-mocks raises “message expectation … has
already been invoked and cannot be modified further” (observed as a
seed-dependent CI failure). The blocks read the mutation ledger at
call time, so later stub_env / hide_env calls only append to it.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/rspec/stubbed_env/stub_helpers.rb', line 75

def install_env_accessors
  allow(ENV).to(receive(:[]) { |key| mixed_env_hash[key] })
  allow(ENV).to(receive(:fetch) do |key, *rest, &default_block|
    hidden = @__rspec_stubbed_env_mutations.reverse.any? do |op, k, _|
      op == :hide && k == key
    end
    value = mixed_env_hash[key]
    if hidden && value.nil?
      unless rest.empty?
        next rest.first
      end
      next default_block.call if default_block

      raise KeyError, %(key not found: "#{key}")
    end
    if value.nil?
      next rest.first unless rest.empty?
      next default_block.call if default_block
    end

    value
  end)
  allow(ENV).to(receive(:values_at)) do |*args|
    args.map { |arg| mixed_env_hash[arg] }
  end
end

#mixed_env_hash ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
# File 'lib/rspec/stubbed_env/stub_helpers.rb', line 112

def mixed_env_hash
  @__rspec_stubbed_env_mutations.each_with_object(ENV.each_pair.to_h) do |mutation, hash|
    operation, key, value = mutation
    case operation
    when :stub
      value.nil? ? hash.delete(key) : hash[key] = value
    when :hide
      hash.delete(key)
    end
  end
end

#remember_hidden_key(key) ⇒ Object



107
108
109
110
# File 'lib/rspec/stubbed_env/stub_helpers.rb', line 107

def remember_hidden_key(key)
  @__rspec_stubbed_env_mutations ||= []
  @__rspec_stubbed_env_mutations << [:hide, key]
end

#remember_stubbed_value(key, value) ⇒ Object



102
103
104
105
# File 'lib/rspec/stubbed_env/stub_helpers.rb', line 102

def remember_stubbed_value(key, value)
  @__rspec_stubbed_env_mutations ||= []
  @__rspec_stubbed_env_mutations << [:stub, key, value]
end

#stub_env(key_or_hash, value = nil) ⇒ Object

Can be called with all key value pairs to be stubbed as a hash:

stub_env('A' => 'B', 'C' => 'D', 'E' => 'F') # Preferred

Alternatively can be called one per ENV key-value pair to stub:

stub_env('A', 'B') # NOT
stub_env('C', 'D') # AS
stub_env('E', 'F') # GOOD (Creates redundant stubs on values_at)


40
41
42
43
44
45
46
47
# File 'lib/rspec/stubbed_env/stub_helpers.rb', line 40

def stub_env(key_or_hash, value = nil)
  init_stub unless env_stubbed?
  if key_or_hash.is_a?(Hash)
    key_or_hash.each { |k, v| add_stubbed_value(k, v) }
  else
    add_stubbed_value(key_or_hash, value)
  end
end

#stub_env_hash_accessors ⇒ Object

Opt in to stubbing ENV hash accessors. By default, stub_env only stubs
ENV[], ENV.fetch, and ENV.values_at so partial ENV stubs stay narrowly
scoped.



52
53
54
55
56
# File 'lib/rspec/stubbed_env/stub_helpers.rb', line 52

def stub_env_hash_accessors
  init_stub unless env_stubbed?
  allow(ENV).to(receive(:to_hash) { mixed_env_hash })
  allow(ENV).to(receive(:to_h) { mixed_env_hash })
end