jimweirich / rspec-given

Given/When/Then keywords for RSpec Specifications
https://github.com/jimweirich/rspec-given
MIT License
653 stars 61 forks source link

Wrong failure message when using StringIO #39

Closed olerass closed 10 years ago

olerass commented 10 years ago
describe 'rspec-given' do
  context 'fails to give the correct message' do
    Given(:str) { '123 789' }
    When(:io) { StringIO.new str }
    Then { io.read(3) == '1234' }
  end
end

Outputs:

Then expression failed at [...]
expected: " 78"
to equal: "1234"
  false   <- io.read(3) == "1234"
  nil     <- io.read(3)
  #<StringIO:0x242cf58>
          <- io

But it should be:

Then expression failed at [...]
expected: "123"
to equal: "1234"
  false   <- io.read(3) == "1234"
  "123"   <- io.read(3)
  #<StringIO:0x242cf58>
          <- io
jimweirich commented 10 years ago

Then clauses (as well as Invariant and And clauses) must be idempotent. That means they need to return the same value every time they are called. This is because parts of the Then expression are reevaluated several times as the subexpressions are printed in the output.

Rewrite your example like this:

describe 'rspec-given' do
  context 'fails to give the correct message' do
    Given(:str) { '123 789' }
    Given(:io) { StringIO.new str }
    When(:result) { io.read(3) }
    Then { result == '1234' }
  end
end

This has the added benefit of emphasizing that it is the io.read call that is being tested, not the StringIO.new.

olerass commented 10 years ago

Of course - you're right. And it even says that in the readme... sorry for the confusion!

jimweirich commented 10 years ago

No problem.

olerass commented 10 years ago

Just an follow up; actually in the example I don't want to test the io.read call. In reality this is what I'd like to do:

context 'returns an IO wrapping the server message specified' do
      When(:msg_io) { TestServerMessage.io :print }
      Then { msg_io.getc.ord == 8 } # Type of SVC_Print
      And  { msg_io.length == 48 } # Length of SVC_Print
    end

The UUT is the TestServerMessage.io method. But to test that it actually wraps the correct object in an IO I have to read a byte from it and check if it is what I expect (not idempotent). I also check the length of the IO but that's safe. I'm not sure how to do this with idempotent then and and operations only.