If let (: thing) is supposed to be written as an example, why am I seeing the value when I run the second example?

We have a template like this in the spec:

describe Thing do
  sanity0 = '../test-data/sanity.mbox'
  let(:sanity1) {
    File.join(@scratch, 'sanity1.mbox').tap { |f|
      FileUtils.cp sanity0, f
    }
  }

  after(:example) do
    FileUtils.rm sanity1
  end

  before(:example) do
    # does stuff using sanity1
    sanity1
  end

  uses_case(proc { [sanity0, sanity1] })

  describe '#method1' do
    it 'does something' do
      # ...
    end

    it 'does another thing' do
      # ...
    end
  end
  # ... more methods ...

end

      

So, sanity1

used from before(:example)

, initialized and then eventually dumped into after(:example)

.

The helper uses_case

contains a shorthand for a before / after pair that creates more data on disk and then deletes it. The list of files passed to is usually passed as a proc, because otherwise you will be calling let

things from the wrong place.

def uses_case(files)
  before do

    # instance_exec is here because RSpec whines if you call the proc
    # directly instead of in the context of the example.
    files = instance_exec(&files) if files.is_a?(Proc)

    # Uses files to initialise case
  end

  after do
    # cleanup stuff
  end
end

      

The problem is, when the second example is executed, it fails:

 [java]       Java::JavaIo::FileNotFoundException:
 [java]         File does not exist: /var/folders/d5/p7yfxkhx4673nd7z7lz_t36c0000gn/T/scratch20170525-83837-1r7t2g/sanity1.mbox

      

So, it somehow entered the second test as a value from the first test ... but the cleanup logic was run after that first test, so the file is now gone.

If I insert the output lines at the appropriate points:

 [java] Thing
 [java]   #method1
 [java] let(:sanity1)
 [java] cleanup(:sanity1)
 [java]     does something
 [java]     does another thing (FAILED - 1)
 [java]   #method2
 [java]     does something (FAILED - 2)
 [java]     does another thing (FAILED - 3)

      

It's so clear that he only really calls it once at the context level.

How can it be? If I write a stand-alone case that only has examples and no other structure, it behaves in a completely opposite way, calling both blocks for each individual example.

Edit: Further shortening, I managed to get it autonomous:

require 'rspec'

def uses_case(files)
  before do
    files = instance_exec(&files) if files.is_a?(Proc)
  end
end

describe 'something' do
  let(:in_describe) { $stderr.puts('let(:in_describe)'); 'd' }

  uses_case(proc { [in_describe] })

  context 'when context 1' do
    it 'does something' do
      $stderr.puts('in example 1-1')
    end
    it 'does something else' do
      $stderr.puts('in example 1-2')
    end
  end
end

      

Result:

$ rspec test_spec.rb 
let(:in_describe)
in example 1-1
.in example 1-2
.

      

If I add more diagnostic data to it, it gets weirder:

--- in before for uses_case ---
self = #<RSpec::ExampleGroups::Something::WhenContext1:0x1722011b>
__memoized = #<RSpec::Core::MemoizedHelpers::ThreadsafeMemoized:0x5b3f61ff>
             contents: {}
--- in let(:in_describe) block ---
self = #<RSpec::ExampleGroups::Something::WhenContext1:0x1722011b>
__memoized = #<RSpec::Core::MemoizedHelpers::ThreadsafeMemoized:0x5b3f61ff>
             contents: {}

after instance_exec
__memoized = #<RSpec::Core::MemoizedHelpers::ThreadsafeMemoized:0x5b3f61ff>
             contents: {:in_describe=>"d"}

in example 1-1
.--- in before for uses_case ---
self = #<RSpec::ExampleGroups::Something::WhenContext1:0x45a4b042>
__memoized = #<RSpec::Core::MemoizedHelpers::ThreadsafeMemoized:0x327af41b>
             contents: {}
after instance_exec
__memoized = #<RSpec::Core::MemoizedHelpers::ThreadsafeMemoized:0x327af41b>
             contents: {}

in example 1-2
.

      

It looks like this, the second time, it is a new hash, and the hash is empty, but the block is let

not called.

+3


source to share





All Articles