Testing files and IO
Dir.mktmpdir
A file test needs a real file; a real file needs a folder nobody else uses. Dir.mktmpdir makes one in the system temporary area, and the block removes it even when the test raises:
require "minitest/autorun"
require "tmpdir"
require "fileutils"
class ReportTest < Minitest::Test
def setup
@dir = Dir.mktmpdir
end
def teardown
FileUtils.remove_entry(@dir)
end
end
The setup/teardown pair is the same guarantee as the block, written where a test class needs the folder in several tests. The folder name is random, so two runs at once do not collide.
The class
The class under test writes a CSV-shaped report into a folder it is given. Taking the folder as an argument is what makes it testable:
class Report
def initialize(dir) = @dir = dir
def write(rows)
path = File.join(@dir, "report.txt")
File.write(path, rows.map { |row| row.join(",") }.join("\n") + "\n")
end
end
The test
The assertion reads the file back and compares the whole content. A test that checks the file exists passes for the wrong reason; the bytes are the behaviour:
def test_writes_rows
Report.new(@dir).write([["a", 1], ["b", 2]])
assert_equal "a,1\nb,2\n", File.read(File.join(@dir, "report.txt"))
end
The expected string contains the exact newlines. A test written with readlines and a chomp would pass while the file gained a blank last line, which is the kind of difference a downstream parser notices.
Capturing output
A method that prints is tested by capturing what it printed. capture_io replaces $stdout and $stderr for the block and returns both:
def test_captures_stdout
out, _err = capture_io { puts "hello" }
assert_equal "hello\n", out
end
ruby test/report_test.rb
.. Finished in 0.001841s, 1086.4281 runs/s, 1086.4281 assertions/s. 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
A test that prints during a run and does not capture is noise in the report. capture_io keeps the assertion on the text and the run quiet.
What to watch for
A test that writes into the project folder leaves a file behind and fails
on the second run when the content is already there. Every write goes
into the temporary folder, and the teardown removes it.
A path with __dir__ in a test points at the test
file, not at the project root. A fixture under
test/fixtures/ is reached from the test file with
File.join(__dir__, "fixtures", name), which works
from any working directory.
Next: Metaprogramming
Article author: Arthur Isaev