Minitest Custom Assertions

Since I discovered the world of testing and TDD I’ve loved the philosophy behind it and the safety net that it brings. My first interaction with testing was with Ruby on Rails specifically with RSpec, but I’ll admit it, understanding and getting used to testing is not easy but RSpec did a very good job at making it easier and in combination with shoulda matchers makes tests wonderfully easy to read. On the frontend we have Cucumber which is like reading a story but can can take a lot of time and effort to build and maintain, which is one of the reasons why the Steak testing framework was built because it was just ruby, and with this idea I am writing this blog post to talk about why I’m starting to love minitest.

Minitest is a small, simple and fast testing framework that not only facilitates testing, TDD, etc, but also provides mocking and benchmarking, and on the comparison before between Cucumber and Steak, RSpec is a DSL while Minitest is just Ruby which not only will it be faster but also you can customize however you want.

Minitest is also part of the standard library meaning that you don’t have to add external libraries which also means that it’s available for you to use in Rails which is what I want to show. Here’s a brief example.

Let’s imagine we have an ActiveRecord model like this…

class Airline < ApplicationRecord
    validates :name, presence: true
    validates :image_url, presence: true

    has_many :reviews

You might want to validate that the name and image_url are present and it’s a very simple assertion, you might create an airline with name and image_url and if you’re going to test the name validation you set it to nil assert that the model is not valid and might also check the errors collection and repeat for the image_url, simple, right?

Now what about the reviews association? we have several options, one is to check if the airline model includes the reviews method via respond_to? but that’s not accurate because we could have a def reviews; nil end method defined which will give a false positive, same if we assert that reviews returns a collection or a number of items if we have a method like this def reviews; [1,2,3] end it’s still a false positive.

A good thing that I liked about minitest was that there is no out of the box way for you to check for ActiveRecord associations like what you can do with RSpec/Shoulda, you need to dig a bit into how ActiveRecord handles and reports associations which is good because you get to know ActiveRecord a bit better.

Now ActiveRecord has a class method reflections that you can use in your models which returns associations and aggregations of Active Record models and classes, we can take advantage of that to make sure that we have an actual association instead of a method getting rid of a false positive.

2.7.1 :001 > Airline.reflections
 => {"reviews"=>#<ActiveRecord::Reflection::HasManyReflection:0x00007ffc1a28d6d8 @name=:reviews, @scope=nil, @options={}, @active_record=Airline (call 'Airline.connection' to establish a connection), @klass=nil, @plural_name="reviews", @type=nil, @foreign_type=nil, @constructable=true, @association_scope_cache=#<Concurrent::Map:0x00007ffc1a28c3f0 entries=0 default_proc=nil>>} 

With this knowledge we can create a custom assertion or a helper method inside our Rails test_helper.rb

# Add more helper methods to be used by all tests here...
def assert_association(model, association, association_type)
    assert_includes model.reflections, association.to_s

    assert_equal association_type, model.reflections[association.to_s].macro

Now in airline_test.rb we can have this test

test 'has_many reviews' do
    assert_association Airline, :reviews, :has_many

Or we can make it a bit more readable by adding two more helper methods inside test_helper.rb

def assert_has_many(model, association)
    assert_association model, association, :has_many

def assert_belongs_to(model, association)
    assert_association model, association, :belongs_to

So we could have…

test 'has_many reviews' do
    assert_has_many Airline, :reviews

and in review_test.rb

test 'belongs_to airline' do
    assert_belongs_to Review, :airline

See? minispec can be very easy to write and to extend to meet your needs.

If you have any comments or questions feel free to reach out on twitter at @fcastellanos.