RubyGuides
Share this post!

A Quick Analysis of How Minitest Works

As you may know, Minitest is the default testing library for Rails & DHH’s favorite.

Some people prefer it for its simplicity & how little code it has compared to its main alternative (RSpec).

As you can see in this picture:

comparison chart minitest vs rspec

Now this post is not about which one you should choose or which is ‘better’.

This post is about how Minitest gets the job done.

If you are wondering: Just use whichever you like the best, but you should still be familiar with both 🙂

So if you like to learn how things work you will enjoy this post, regardless of what testing library is your favorite.

Let’s Have a Look Under The Hood

One of the things that people recommend (including me) is to read source code because it’s a great way to learn how things work & also it’s a great way to pick up some new Ruby tricks that you may not have seen before.

That’s what I did with Minitest & I’m going to share with you what I learned.

Let’s start with some actual test code so we can discuss how this relates to how Minitest does things.

class Thingy < Minitest::Test
  def test_it_works
    assert_equal 1, 1
  end
end

So how does Minitest find these testing methods (like test_it_works) & run them?

The answer is a little bit of metaprogramming 'magic':

def self.methods_matching(re)
  public_instance_methods(true).grep(re).map(&:to_s)
end

This comes from the Runnable class which is defined in lib/minitest.rb. This code finds all the instance methods for the current class & selects the ones that match a regular expression.

So if you call methods_matching(/^test_/) you will get an array with all the method names that start with test_.

Minitest does this for you & calls these methods.

That happens in the lib/minitest/test.rb file (and to be more specific, on the runnable_methods method, which also returns the list of methods in random order).

Important point:
This works because Minitest::Test is a subclass of Runnable.

The final piece of the puzzle is the run class method on Runnable, which does some additional filtering & then calls run_one_method with every method name & a reporter object.

Here's the code:

filtered_methods.each do |method_name|
  run_one_method self, method_name, reporter
end

And this ends up calling the run instance method on Minitest::Test:

capture_exceptions do
  before_setup; setup; after_setup

  self.send self.name
end

Send is a metaprogramming method that lets you call another method on any object using a string or a symbol.

The capture_exceptions block is used to record test failures & exceptions raised by your code.

def capture_exceptions # :nodoc:
  yield
rescue *PASSTHROUGH_EXCEPTIONS
  raise
rescue Assertion => e
  self.failures << e
rescue Exception => e
  self.failures << UnexpectedError.new(e)
end

This is how I like to read code, focus on one aspect or feature from the code you are reading & then keep peeling the layers off like an onion.

If you don't know what something means, like this yield keyword, then look it up.

It's part of the learning process!

Conclusion

In this post you learned how Minitest uses metaprogramming to find your test methods & call them. You also learned how test errors & exceptions are captured into an array for reporting.

Do you like this kind of "code analyzed" articles? Let me know in the comments 🙂

Also don't forget to share this on your favorite social networks & subscribe to my newsletter below if you aren't already part of 6000+ other Ruby developers like you that are looking to improve their skills!