Reputation: 3049
I am trying to write a spec to test displaying a tic-tac-toe board. I am using the method capture
but it throws an error when I run the spec. I am using capture to get the output of a method call. https://apidock.com/rails/Kernel/capture
Here is my method:
def display_board
puts " #{grid[0]} | #{grid[1]} | #{grid[2]} "
puts "-----------"
puts " #{grid[3]} | #{grid[4]} | #{grid[5]} "
puts "-----------"
puts " #{grid[6]} | #{grid[7]} | #{grid[8]} "
end
Here is my Rspec test:
context "#display_board" do
output = capture(:stdout) { board.display_board}
rows = output.split("\n")
binding.pry
expect(rows[0]).to eq(" | | ")
expect(rows[1]).to eq("-----------")
expect(rows[2]).to eq(" | | ")
expect(rows[3]).to eq("-----------")
expect(rows[4]).to eq(" | | ")
end
Upvotes: 2
Views: 1116
Reputation: 583
capture
was removed from Rails, but I liked it and brought it back, mostly for use in my spec suites. I extracted the exact Rails code, along with the complete test suite, and then improved the test suite. The gem is called silent_stream
, it works with Rails 5, 6, and partially with rails 7. You can find it here: https://github.com/pboling/silent_stream
This is how you use it for output capturing:
# Make the methods avaialble:
RSpec.configure do |conf|
conf.include SilentStream
end
# Then add an expectation on output:
it 'has output' do
output = capture(:stdout) { subject.request(:get, '/success') }
logs = [
'INFO -- request: GET https://api.example.com/success',
'INFO -- response: Status 200'
]
expect(output).to include(*logs)
end
Recent versions of RSpec have an output matcher built-in! Use this instead.
https://www.rubydoc.info/gems/rspec-expectations/RSpec%2FMatchers:output
Upvotes: 1
Reputation: 2750
The problem is that #capture
was deprecated and removed.
Personally, I would separate I/O from the generation of the game content, because it's easier to work with and test that way. So instead of puts
, just build up a string containing your board (with a \n
at the end of each line) and test that for correctness. In the actual game, output the board to the console with one puts
call.
If you really want to capture STDOUT, there is a way. $stdout
is a global containing a reference to the current STDOUT stream. By default this is always set to STDOUT
, a constant representing the actual STDOUT of the program, but you can redirect $stdout
to another IO stream, for example a StringIO
object. puts
will write to whatever $stdout
refers to instead of directly writing to STDOUT
(there is a subtle difference there!).
$stdout = StringIO.new
puts 'this is my output'
captured_output = $stdout.string
$stdout = STDOUT # restore it when you're done
Now you have 'this is my output' (followed by a newline character) captured in captured_output
. But you'll also have anything else that went to STDOUT in that time, so it's not ideal (e.g. console output in your ruby console). You should wrap that in a method that ensures it's always restored, e.g.
def capture()
$stdout = StringIO.new
yield
$stdout.string
ensure
$stdout = STDOUT
end
You can use this with a block, e.g.
capture { puts 'xyz' }
=> 'xyz\n'
(But you'll have an easier life if you forget this approach and just build your board up as a string.)
Upvotes: 2