StringIO in Ruby: How it Works & How to Use it

Are you looking for an object that behaves like an IO object (files, sockets, etc), but that you can control like a string?

Then StringIO is for you.

Let me show you some examples & things to watch out for!

Basic Examples

To create a StringIO object you can do this:

io = StringIO.new("abc")

Then you can read from this object using methods like gets, read & each_line.

I made a handy table for you with the most useful methods:

Method Description
gets Read one line of input
read Read a specific amount of bytes (all by default)
each_line Given a block, iterate over each line
each_char Given a block, iterate over each character
<< Append data
rewind Reset the internal position pointer
string Returns an actual string from the stringio object

Notice that StringIO has a position pointer.

This pointer keeps track of how many bytes you have read, just like a file object does.

So every time you call a method like gets or read it will give you that amount of data & advance the pointer.

Even Enumerable methods like map or each_line will advance the position pointer, so keep that in mind.

io.each_line { |line| puts line }

You can reset the position pointer to the beginning using the rewind method:

io.pos
# 45

io.rewind

io.pos
# 0

That’s it for the basics.

I’m going to show you some practical uses for StringIO, but first let me show you something else.

What About StringScanner?

Now you have seen what StringIO can do, but the Ruby Standard Library includes another string-related class.

That class is StringScanner.

It can be confusing since they have similar names & methods, but let me help you see the difference.

The main thing is this:

A StringIO object can replace another IO object (like a File or a Socket), but StringScanner is meant for doing things like parsing (making sense of some text by breaking it into a set of tokens).

These two classes still share something, besides having “String” in their name, they both use an internal position pointer.

Replacing Standard Input & Output

Let’s say you are writing a command-line application that ask user for output using the Kernel#gets method…

…if you want to test this code you are going to have to input something by hand every time.

So is automated testing out of the picture?

No, it isn’t!

This is where StringIO comes to the rescue. You can initialize a StringIO object with your test input & then replace the standard input object pointed by $stdin (this where Ruby looks for user input when you call gets).

Example:

io     = StringIO.new("input")
$stdin = io

gets
# input

This technique can also be used to capture output from methods like puts.

Display methods print to the default output device, known as ‘standard output’. In Ruby this is represented by an IO object which you can replace with your StringIO object.

Example:

io      = StringIO.new("")
$stdout = io

# Print to $stdout
puts "Jesus Castello is from Spain & likes to help people learn Ruby."

# Restore original value
$stdout = STDOUT

io.rewind
io.read
# "Jesus Castello is from Spain & likes to help people learn Ruby."

This is more involved than the input version, because we want to make sure to restore the original STDOUT object & to rewind our StringIO so we can read the output.

Notice that most testing frameworks include a method to do this for you (assert_output for Minitest & the output matcher in RSpec), but it’s always great to know what’s going on behind the scenes 🙂

Summary

You learned about the StringIO class, which emulates a real IO object so it can act as a replacement for that kind of object.

This can be useful for testing classes that write output to the screen or require user input via the terminal.

If you know about some interesting uses for StringIO let us know in the comments & don’t forget to share this article so more people can enjoy it!

2 thoughts on “StringIO in Ruby: How it Works & How to Use it”

  1. First, thanks for an awesome site and very nice examples!

    A flaw in this post

    ‘…You wrote ‘Let’s say you are writing a command-line application that ask user for output using the…’

    I think you mean:

    ‘…Let’s say you are writing a command-line application that ask user for input using the…’

Comments are closed.