Ruby: Testing Articles

Testing in Ruby

Ruby is unusual among languages in that the decision about which test framework to use is already made for you, at least to begin with. Minitest is bundled with Ruby rather than built into it, so a fresh installation can already run a test suite without anything being installed (although a project managed with Bundler still has to declare it in the Gemfile). The choice that remains is whether to keep it, and that choice is more about the style of the tests than about their capabilities.

Minitest, the framework already installed

Minitest is small on purpose. Its assertion style (assert_equal, assert_nil, refute) is plain Ruby: a test is a method on a class, and there is very little between the code being tested and the code doing the testing. That shallowness is the point, because a failing test tends to point at the application rather than at the framework.

It also offers a spec style, with describe and it, for teams that prefer the shape of a specification without switching frameworks. Rails generates Minitest tests by default, which is why most Rails applications start with it.

RSpec, and what the extra syntax buys

RSpec is an external gem with a much larger surface: let and subject for lazily built test data, a wide matcher library, shared examples, and its own mocking framework. The cost is that the suite acquires a vocabulary of its own that has to be learned alongside Ruby.

The benefit shows up on long-lived suites, where the descriptive output reads as documentation of the behavior and a well-named context tells a newcomer why a case exists. Neither framework catches bugs the other misses. What they change is how much structure the suite carries, and how much of that structure has to be maintained. We put both side by side, writing the same suite twice, in Testing in Ruby: From Scratch to Minitest and RSpec.

Where the real difficulty lives

The framework is rarely what makes a test suite hard to keep. The recurring problems sit underneath it:

  • Test data: fixtures declared once for the whole suite, or factories built per example. One is fast and rigid, the other flexible and easy to overuse.
  • Test doubles: Minitest::Mock, which moved out to the separate minitest-mock gem in Minitest 6, and the doubles built into RSpec both let a test stand in for a collaborator. Overusing either ties the test to the way the code is written rather than to what it does, and the test may start failing while the behavior is still correct.
  • Web applications: Rack::Test exercises any Rack application, Sinatra and Rails included, without starting a server. It is the layer where request and response are tested directly.