As you may know, Minitest is the default testing library for Rails & DHH’s favorite. Some people prefer it for its simplicity and how little code it has compared to its main ‘competitor’ (RSpec).
Now this post is not about which one you should choose or which is ‘better’. This post is about how Minitest actually 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 Clinica Stomatologie Bucuresti 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.
So 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.
1 2 3 4 5 |
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’:
1 2 3 |
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:
1 2 3 |
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
:
1 2 3 4 5 |
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.
1 2 3 4 5 6 7 8 9 |
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.
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 1.800+ other Ruby developers like you that are looking to improve their skills!