Testing RSpec model validations

I recently came across an issue while trying to write RSpec tests for model validations that I think are worth addressing. I think it’s worth testing validations because they may change over time and you want to make sure your test suite is accounting for them. For example, I have character limits for messages, which is a critical model validation. If I were to accidentally delete the validation or change the character limit, there would be a lot of unexpected consequences.

The first validation I wanted to test was a character limit on messages that was only triggered if the message was sent by a user. To do so, I created a from_user method that checks to see if the message.sender_type == 'user'. The model looks like this:

class Message < ActiveRecord::Base
  validates_presence_of :body, :status, :contact
  validates :body, length: { in: 1..420 }, if: :from_user?, on: :create

  def from_user?
    self.sender_type == 'user'
  end
end

At first, it seemed like I could easily use thoughtbot’s shoulda gem to write a simple test.

describe 'character limit for user-sent messages' do
  it {should_validate_length_of(:body).is_at_least(1).is_at_most(420).on(:create) }
end

However, the validation kept failing. After a little bit of digging, I realized it was because I never specified a subject. In particular, I never specified that my subject was a user-sent message. I found that there are two solutions to this problem.

The first is to specify a subject with a FactoryGirl.build. It’s important that you use ‘build’ and not ‘create’, because the ‘create’ method will trigger all model validations, which is exactly what you’re trying to test. In addition, when you ‘build’ the model, make sure you leave out the attribute that you’re testing. In my case, I’m trying to test a body validation, so it wouldn’t make sense to do a FactoryGirl.build where I specify the message body.

The second solution is to write a ‘before’ hook that lets the test assume that the subject is from a user, which will trigger the validation. Either method works well, but I opted to use the second option to practice a new technique.

Option 1

describe 'character limit for user-sent messages' do
  subject { FactoryGirl.build :message, contact: contact, status: 'sent' }
  it {should_validate_length_of(:body).is_at_least(1).is_at_most(420).on(:create) }
end

Option 2

describe 'character limit for user-sent messages' do
  before { allow(subject).to receive(:from_user?).and_return(true) }
  it {should_validate_length_of(:body).is_at_least(1).is_at_most(420).on(:create) }
end

Keeping track of Rails transactions

Ever heard of transaction_include_any_action?([:action])? You may never need to use this specialized part of ActiveRecord, but I thought I would shed some light on it anyway.

Recently, I was writing a module that helped incorporate several models into Elasticsearch. Concerns::Searchable, as I named it, helps create mappings for models, provides a base method for specialized searches, and also has an after_commit hook to index records. I first looked into ActiveRecord::Transactions to customize when a record is indexed into Elasticsearch.

module Concerns
  module Searchable
    extend ActiveSupport::Concern

    included do
      include Elasticsearch::Model
      after_commit :update_elasticsearch_index

      ...

      def update_elasticsearch_index
        if transaction_include_any_action?([:destroy]) || (transaction_include_any_action?([:update]) && self.archived?)
          ElasticsearchIndexWorker.perform_async(:delete, self.id)
        else
          ElasticsearchIndexWorker.perform_async(:index, self.id)
        end
      end
    end
  end
end

What I wanted was for Elasticsearch to delete records that were deleted in Postgres OR “archived”. That way, when a user goes to search for a record, they won’t accidentally get results from “archived” records that are being kept in Postgres for other purposes. Using ActiveRecord::Transactions helped me customize the logic for indexing after_commit.


NOTE: One small hiccup you may encounter is that the argument passed to the method has to be an array.

This is okay:

transaction_include_any_action?([:destroy, :update])

This is not:

transaction_include_any_action?(:destroy)

Adding foreign key constraints in Rails

Here’s a great article about adding foreign key constraints to handle cascading deletion of records. Basically, it’s a good idea to handle foreign key constraints through the database because Rails doesn’t always fire an object’s callbacks upon deletion. So if you have a dependent: :destroy callback set up, it might get skipped in some situations.

https://robots.thoughtbot.com/referential-integrity-with-foreign-keys

Include vs. Extend…why not both?

If you don’t already know, there is a subtle difference between including and extending a module into one of your classes. include allows you to access instance methods while extend allows you to access class methods. See the example below for clarification:

module FooModule
  def foo
    puts 'foo'
  end
end

----------

class Bar
  include FooModule
end

Bar.new.foo => 'foo'
Bar.foo => NoMethodError: undefined method 'foo'

----------

class Bar
  extend FooModule
end

Bar.new.foo => NoMethodError: undefined method 'foo'
Bar.foo => 'foo'

So what if you want to create a module that will grant you access to both class and instance methods simultaneously? Luckily, using Rails’ ActiveSupport::Concern will help you separate your instance and class methods easily.

module FooModule
  extend ActiveSupport::Concern

  def foo
    puts 'foo'
  end

  module ClassMethods
    def baz
      puts 'baz'
    end
  end
end

----------

class Bar
  include FooModule
end

Bar.new.foo => 'foo'
Bar.foo => NoMethodError: undefined method 'foo'
Bar.new.baz => NoMethodError: undefined method 'foo'
Bar.baz => 'baz'

Rails – tools for testing

One of the great things about Ruby and Rails is the sheer number of gems out there that are available to make your life easier. There are many gems out there that are designed to help make your work better and even reduce the effort required to produce beautiful code. I’m going to outline a few of the gems that I’ve been using a lot recently while learning how to make Rails apps, specifically for testing. These gems are extremely useful tools in making sure that your code functions as expected and can increase your productivity.


quiet_assets

quiet_assets allows you to turn off the overwhelming amount of messages that are printed in the rails server log. It will help you keep track of the important things going on in your rails app without having to read about how your app is accessing the database.

GitHub repo

rspec-rails

rspec-rails is just the rails version of the popular testing gem, rspec. It’s a great way to use TDD to help create your app. It’s use will be very familiar if you’ve ever use rspec to test Ruby code. However, it’s definitely got a whole other suite of methods that are useful for testing your code in Rails. If you want to start getting familiar with it’s use, visit the Relish documentation.

If you’re going to use rspec-rails, don’t forget to add some code into your spec_helper.rb to reduce the amount of error messages that you have to read.

GitHub repo

capybara

capybara is another great rails testing tool based on Behavior Driven Development. It is used to create tests that predict how a user might interact with your rails app. In this way, it makes it easier to identify small problems that would detract from a user’s experience.

For example, I’ve written a test below that describes a scenario where a user might want to create a new profile. The great thing about capybara is that it uses syntax that reads very close to a natural description of what a user would do to create a new profile. This makes it easier to write simple, clear tests so that you can focus more on writing code.

require 'rails_helper'

feature 'Creating a new profile' do
  scenario 'should allow a user to make a profile' do
    visit '/'

    click_button 'Sign Up'

    expect(page).to have_content('Thank you for signing up!')

    fill_in 'First Name', with: 'Tim'
    fill_in 'Last Name', with: 'Park'
    fill_in 'Date of Birth', with: '01/01/01'
    fill_in 'Phone Number', with: '1234567890'
    fill_in 'Email', with: 'timsjpark@gmail.com'
    
    click_button 'Save new profile'

    expect(page).to have_content('Welcome to your new home page!')
  end
end

GitHub repo

shoulda-matchers

shoulda-matchers allows you to write simple code for complex tests for common Rails functions. You can test if a link does a redirect_to a page or validate_inclusion_of a required field in a form submission.

GitHub repo

factory_girl_rails

factory_girl_rails helps create stock objects with extremely simple code. It takes the time and effort required to define something like a product before writing a test for it. Instead of generating a fake product by calling on your model, why not let factory_girl do all of the hard work.

product = Product.create(type: 'Computer', make: 'Company X', model: X100, color: black, price: 2000)

The factory_girl_rails way

product = FactoryGirl.create(:product)

GitHub repo

faker

faker is an amazing gem that can populate your database with fake data. It can accommodate a number of varied types of data such as names, addresses, commercial products, dates, and even entire paragraphs. When used in tandem with factory_girl_rails, it’s extremely valuable for creating test data that will help your Rails app feel like a real product.

GitHub repo


Remember, Test and Behavior Driven Development are important tools in quickly writing code that works well. But if you’re spending all of your time trying to figure out how to write tests and specify how you want your app to behave, then you end up being counter-productive. These gem will help remove the effort required to write good tests so you can get out there and have fun building Rails apps.