What is Minitest?
Minitest is a Ruby testing library, it allows you to write tests for your code TDD style.
It’s the default testing framework 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:
Now this post is not about which one you should choose or which is ‘better’.
This post is about how Minitest works.
If you are wondering: Just use whichever you like the best, but you should still be familiar with both 🙂
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_
.
How Minitest Works
Minitest finds these test_
methods, then it calls them.
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 7000+ Ruby developers like you that are looking to improve their skills!