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:
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.
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
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).
This works because
Minitest::Test is a subclass of
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
capture_exceptions do before_setup; setup; after_setup self.send self.name end
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!
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!