RSpec Testing with Rails

Posted by Eli Cusic on April 23, 2021

I need to redo a project, so I figured this was a great opportunity to begin implementing tests. I decided to use RSpec and keep things very simple. First of all, I needed to add the rspec gem to the gemfile:

group :development, :test do
  gem 'rspec-rails', ">= 3.9.0"
end

Run bundle install and now RSpec is available to use. Next step is creating a spec directory with a models directory under that, and then we can create a file for a model tester. In this case, we will test the model Deck, which will have name and level attributes. So, creating the file deck_spec.rb - and this is where our tests will go for this ruby model:

# ./spec/models/deck_spec.rb

require 'rails_helper'

RSpec.describe Deck, :type => :model do
  it "is valid with valid attributes"
  it "is not valid without a name" 
  it "is valid without a level" 
end

Ok, so this is the basic syntax for setting up our model tester. The lines within the method however don’t actually conduct any specific test. Instead, those lines in quotations are what will appear as the description for the test. So, when we run rspec ./spec/models/deck_spec.rb in the command line we’ll see this:

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Deck is valid with valid attributes
     # Not yet implemented
     # ./spec/models/deck_spec.rb:18

  2) Deck is not valid without a name
     # Not yet implemented
     # ./spec/models/deck_spec.rb:19

  3) Deck is valid without a level
     # Not yet implemented
     # ./spec/models/deck_spec.rb:20


Finished in 0.00386 seconds (files took 1.32 seconds to load)
3 examples, 0 failures, 3 pending

As you can see, the description is given at each number, but there are no tests implemented. So let’s write a test!

RSpec.describe Deck, :type => :model do
  it "is valid with valid attributes" do 
    expect(Deck.new).to be_valid
  end
  it "is not valid without a name" 
  it "is valid without a level" 
end

Now when we run the tests, we should get one less test pending:


Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Deck is not valid without a name
     # Not yet implemented
     # ./spec/models/deck_spec.rb:21

  2) Deck is valid without a level
     # Not yet implemented
     # ./spec/models/deck_spec.rb:22


Finished in 0.02104 seconds (files took 0.96134 seconds to load)
3 examples, 0 failures, 2 pending

Ok, let’s take this up a notch and add a validator to the name attribute in our Deck model

class Deck < ApplicationRecord
    has_many :cards
    validates :name, presence: true
end

And now when we run the same test, we’ll get an error

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Deck is not valid without a name
     # Not yet implemented
     # ./spec/models/deck_spec.rb:21

  2) Deck is valid without a level
     # Not yet implemented
     # ./spec/models/deck_spec.rb:22


Failures:

  1) Deck is valid with valid attributes
     Failure/Error: expect(Deck.new).to be_valid
       expected #<Deck id: nil, name: nil, level: nil, created_at: nil, updated_at: nil> to be valid, but got errors: Name can't be blank
     # ./spec/models/deck_spec.rb:19:in `block (2 levels) in <top (required)>'

Finished in 0.02622 seconds (files took 0.84503 seconds to load)
3 examples, 1 failure, 2 pending

Failed examples:

rspec ./spec/models/deck_spec.rb:18 # Deck is valid with valid attributes

Alright, now let’s round this out and write out tests for each description we’ve made

RSpec.describe Deck, :type => :model do
  deck = Deck.new(name: "New Deck")
  it "is valid with valid attributes" do 
    expect(deck).to be_valid
  end
  it "is not valid without a name" do 
    expect(Deck.new).to_not be_valid 
  end
  it "is valid without a level" do 
    expect(deck).to be_valid 
  end
end

Ok, let’s run the test once more, and we should get this:

Finished in 0.01515 seconds (files took 0.82945 seconds to load)
3 examples, 0 failures

Glorious!