Gregory Jaros
Gregory Jaros

Reputation: 145

RSpec not passing 'should save' test

Link to Repo

I'm new to testing Rails so this is probably a very small thing but I can't figure out what's wrong. So I have some models I'd like to test. Right now the tests are simple; testing the presence of attributes and saving if all validations are met.

One of my models Profile belongs_to my Users model and passes all these tests spec/models/profiles_spec.rb:

require 'rails_helper'

RSpec.describe Profile, type: :model do
  context 'validation tests' do
    it 'ensures user_id presence' do
      profile = Profile.new(platform: 0, region: 0, tag: 'GamerTag', sr: 1600).save
      expect(profile).to eq(false)
    end

    it 'ensures platform presence' do
      profile = Profile.new(user_id: 1, region: 0, tag: 'GamerTag', sr: 1600).save
      expect(profile).to eq(false)
    end

    it 'ensures region presence' do
      profile = Profile.new(user_id: 1, platform: 0, tag: 'GamerTag', sr: 1600).save
      expect(profile).to eq(false)
    end

    it 'ensures tag presence' do
      profile = Profile.new(user_id: 1, platform: 0, region: 0, sr: 1600).save
      expect(profile).to eq(false)
    end

    it 'ensures sr presence' do
      profile = Profile.new(user_id: 1, platform: 0, region: 0, tag: 'GamerTag').save
      expect(profile).to eq(false)
    end

    it 'should save successfully' do
      profile = Profile.new(user_id: 1, platform: 0, region: 0, tag: 'GamerTag', sr: 1600).save
      expect(profile).to eq(true)
    end
  end
end

app/models/profile.rb:

class Profile < ApplicationRecord
  validates :platform, presence: true
  validates :region, presence: true
  validates :tag, presence: true
  validates :sr, presence:true

  belongs_to :user

  enum platform: [:pc, :xbl, :psn]
  enum region: [:us, :eu]
end

But then there are my other models, which "pass" all the attribute presence validation tests, which theres something wrong there because they still pass when I comment out their attribute validations and fail the 'should save successfully' test.

The most confusing part? When I run the rails console and manually test it returns the expected value (true), like with my Student model which belongs_to :profile.

So I really have no idea what's going on here. Any ideas please throw them out. If you all need any more information, let me know.

Upvotes: 1

Views: 3602

Answers (2)

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230521

Indeed, it's an error of missing related records. Let's take coach spec, for example:

it 'should save successfully' do
  coach = Coach.new(profile_id: 1, roles: ['tank']).save
  expect(coach).to eq(true)
end

Here (in my experiments) there's no profile with id=1. In fact, there are no profiles at all. So this spec fails, as expected.

Buuuut, by the time we get to the profile spec:

it 'should save successfully' do
  profile = Profile.new(user_id: 1, platform: 0, region: 0, tag: 'GamerTag', sr: 1600)
  expect(profile).to eq(true)
end

user with id=1 does exist (likely because user spec was run before this one and successfully created a user record).

Lessons to learn:

  1. Always clean/rollback database between tests (or otherwise make it pristine)
  2. Always run tests in a randomized order. Spec order dependency can be very difficult to detect, as you can see in this thread.

Upvotes: 3

arieljuod
arieljuod

Reputation: 15848

First of all, you are writing tests in a really inefficient way. If you wan't to test validations, then you don't need to test the save method return value but the value of the valid? method AND the errors hash.

RSpec.describe Profile, type: :model do
  context 'validation tests' do
    it 'ensures user_id presence' do
      profile = Profile.new(platform: 0, region: 0, tag: 'GamerTag', sr: 1600, user_id: nil) #you should be explicit with the user_id value being nil, tests should be explicit, it may seem unnecesary but it makes them easier to read
      expect(profile).to be_invalid #or expect(profile).not_to be_valid
      expect(profile.errors[:user_id]).to be_present #you could test the actual message too, not just the presence on any error
    end
  end
end

That test actually tests only validations and also ensures that there's an error on the user_id field.

With your actual test you cannot know what is actually preventing the object to be saved. It could be anything: another validation, a before_save callback returning false, an invalid value when inserting into the database, anything. It's also slower since it has to actually write the record on the database, testing valid? is done on memory which is a lot faster.

Id' recommend you to read about FactoryBot so you don't have to repeat Profile.new.... on each test.

If you still want to test the return value of save on the last test, you have to know WHY it's not being saved, you con use save! which raises an exception instead of returning false to debug your code, and you can also inspect profile.errors.full_messages to see if there are any errors that you didn't consider when setting up the test.

Upvotes: 1

Related Questions