0
votes

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.

2
@SergioTulentsev I don't think I need a user to exist. When I go into my db folder and delete development.sqlite3, test.sqlite3 and schema.rb, run rails db:migrate and then rspec, the profile_spec.rb still passes all tests. This is Rails 5.2 - Gregory Jaros
Ah, looks like I completely misunderstood the question... - Sergio Tulentsev
@SergioTulentsev So why does profile_spec.rb's should save successfully test pass and the other model's versions of the test don't? They all have the same basic tests as profile_spec.rb, they all have foreign keys in the schema and belongs_to associations in their models. There is SOMETHING different about Profile but I don't understand what it is. Unless I'm misunderstanding you I still don't see what's causing this. - Gregory Jaros
If you cook up a minimal reproducible example with both "good" and "bad" files, I'll take a look. - Sergio Tulentsev
@SergioTulentsev I added a link to the repo at the top of the post. The state of my project's models and tests right now are still very simple so I don't know how I could make it easier to diagnose. - Gregory Jaros

2 Answers

2
votes

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.
1
votes

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.