Testing Models With RSpec

Testing models with RSpec is essentially one of its primary uses. After all, you do want to make sure your backend logic is working, and models are where most of that logic should go.

For more information about testing, Everyday Rails Testing With RSpec is a great source for learning more about testing Ruby with RSpec. A lot of the following was built using this as a guide.

Model To Be Tested

First it’s important to make sure that when you ran your RSpec install command from the terminal that a spec folder was created. From here if it’s not already created you need to create a model folder inside that spec folder, and from there a new file named “your file name” _spec. The model I’m going to test, creates new force users. Force users have a title, a name, and a location. The model uses Rails to validate that these items are present, and that names are unique. The method name joins the title and name together, and the method self.by_letter orders name’s by letter. The toggle_side! method allows a force user to toggle between light and dark sides of the force.


class ForceUser < ActiveRecord::Base

  validates :title, presence: true
  validates :name, presence: true, uniqueness: true
  validates :location, presence: true

  def name
    [title, name].join(' ')
  end

  def self.by_letter(letter)
    where("name LIKE ?", "#{letter}%").order(:name)
  end

  def toggle_side!
    update(light: !light)
  end
end

Require RSpec

The following code is written in the before mentioned _spec file. Each spec file must require RSpec in order to work. So at the top of your _spec file enter:

require 'rails_helper'

Test For Valid Model Data

This tests whether or not a new force user’s name, title, and location were created.


describe ForceUser do
  it "is valid with a title, name, and location" do
    forceuser = ForceUser.new(
    title: 'Jedi Master',
    name: 'Yoda',
    location: 'Jedi Temple')
    expect(forceuser).to be_valid
  end
end

Test For Missing Model Data

This test makes sure that a blank name is unacceptable. Although it’s not below, similar tests should be constructed to test that title and location are not blank as well.


describe ForceUser do
  it "is invalid without a name" do
    forceuser = ForceUser.new(name: nil)
    forceuser.valid?
    expect(forceuser.errors[:name]).to include("can't be blank")
  end
end

Test For Duplicate Model Data

This test makes sure that each force user’s name is not identical.


describe ForceUser do
  it "is invalid with a duplicate name" do
    ForceUser.create(
      title: 'Jedi Master', name: 'Luke Skywalker',
      location: 'Missing'
    )

    forceuser = ForceUser.new(
      title: 'Sith Lord', name: 'Luke Skywalker',
      location: 'Missing'
    )
    forceuser.valid?
    expect(forceuser.errors[:name]).to include("has already been taken")
  end
end

Test For Combined Model Data


describe ForceUser do
  it "returns a Force User's title and name as a string" do
    forceuser = ForceUser.new(title: 'Jedi Master', name: 'Yoda', location: 'Jedi Temple')
    expect(forceuser.name).to eq 'Jedi Master Yoda'
  end
end

Test Model For Name Sort

This model tests when the letter “L” is entered that Lando and Luke are returned. Another test should be created to make sure that Yoda was omitted.


describe ForceUser do
  it "returns a sorted array of results that match" do
    luke = ForceUser.create(
      title: 'Jedi Master',
      name: 'Luke Skywalker',
      location: 'Missing'
    )
    lando = ForceUser.create(
      title: 'General',
      name: 'Lando Calrissian',
      location: 'Missing'
    )
    yoda = ForceUser.create(
      title: 'Jedi Master',
      name: 'Yoda',
      location: 'Jedi Temple'
    )
    expect(ForceUser.by_letter("L")).to eq [lando, luke]
  end
end

Test Model For Name Sort With Before (DRYer Version)

This test is essentially the same, creating the omission test, would not be very dry, so this chunk of code uses “before” to create force users before running of set tests using “context.”


describe ForceUser do
  describe "filter last name by letter" do
    before :each do
      @luke = ForceUser.create(
        title: 'Jedi Master',
        name: 'Luke Skywalker',
        location: 'Missing'
      )
      @lando = ForceUser.create(
        title: 'General',
        name: 'Lando Calrissian',
        location: 'Missing'
      )
      @yoda = ForceUser.create(
        title: 'Jedi Master',
        name: 'Yoda',
        location: 'Jedi Temple'
      )
      end

    context "with matching letters" do
      it "returns a sorted array of results that match" do
        expect(ForceUser.by_letter("L")).to eq [@lando, @luke]
      end
    end

    context "with non-matching letters" do
      it "omits results that do not match" do
        expect(ForceUser.by_letter("L")).not_to include @yoda
      end
    end
  end
end

Testing Booleans

This tests the toggle method and makes sure that a force user can switch from light to not light (aka the dark side).


describe ForceUser do
  describe '#toggle_side!' do
    it 'should make a force user that is not light turn to light' do
      light = ForceUser.create(light: false)
      light.toggle_side!
      expect(light.complete).to eq(true)
    end
  end

Leave a comment