One of the more complicated Ruby/Rails projects we work with at Highgroove has many points where it interacts directly with the filesystem.
Writing tests for an application whose code requires reading from or writing to the filesystem presents challenges, especially if done naively.
While it’s tempting to simply use the real filesystem during unit tests, this presents a few problems:
- The tests may be brittle, breaking on systems that are not setup just like the initial developer’s local environment.
- Setting up fixtures on the real filesystem may not be plausible; for instance, if the code interacts with system files (such as in the example coming below!).
- Test code must be careful to clean up afterwards, even in error cases. Otherwise, the file system could be left polluted, dirtying the developer’s machine and possibly breaking tests on the next run.
- Tests running in parallel may interact with one another, causing random failures (e.g., on a continuous integration server or with parallel_tests).
- The filesystem is slow; when attempting to make unit tests as fast as possible, the time to write, sync, and/or read from the filesystem may become significant.
So what’s the solution? Fake the filesystem during unit tests.
More after the break.
Consider the following (contrived) piece of code:
Now, what’s the best way to test it? The tests can’t touch /etc/passwd due to permissions, and even if they could, writing to /etc/passwd would be destructive to the system.
How about mocking
This works, but tightly couples the test code. What if someone comes along and refactors the production code, making use of
File.readlines instead of
The code still works, but the test breaks because
File.read is stubbed, not
File.readlines. In fact, it’s now reading from the real system’s /etc/passwd. The code still works, but the test broke. Ugh!
A better solution is to stub the entire filesystem at a higher level, giving a blank slate each time a new test runs. Nothing is ever actually written to the filesystem.
Enter the fakefs gem.
It’s trivial to get up and running. If using bundler, I recommend adding it to the
Next, configure RSpec to include the fakefs helpers to automatically activate and deactivate fakefs whenever a test is tagged with
And finally, write tests that are tagged with
fakefs: true. But this time, test code can manipulate any part of the filesystem, knowing it’s completely emphemeral and isolated:
Voila! While there is a bit more test code, the test is verifying behavior, not the specific implementation. Do you have any other tips for testing code that interacts with the filesystem?