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