2

I have trouble understanding the behavior of Rspec (rspec 3.7, rails 5.1.4) not throwing exceptions (as expected) related to database constraints.

Assuming one creates a table with a not null constraint on a association something like this:

create_table :ce_teams do |t|
  t.string :name
  t.integer :evaluation_id, null: false

  t.timestamps
end

Corresponding model is this:

module Ce
  class Team < ApplicationRecord
    belongs_to :evaluation
  end
end

Then within console creating the Team object throws an ActiveRecord::NotNullViolation exception:

#\>RAILS_ENV=test rails c                                                                                                                                                                                                                                                              
Loading test environment (Rails 5.1.4)
2.4.2 :001 > t1 = Ce::Team.create(name: 'A-Team')                                                                                                                                                                                                                                                                             
   (0.2ms)  BEGIN
  SQL (1.2ms)  INSERT INTO "ce_teams" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["name", "A-Team"], ["created_at", "2017-11-17 18:13:17.444797"], ["updated_at", "2017-11-17 18:13:17.444797"]]
   (0.2ms)  ROLLBACK
ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR:  null value in column "evaluation_id" violates not-null constraint

Running the same from within rspec does not:

  it 'throwing not null exception if evaluation is missing in ctor' do
    expect {
      Ce::Team.create(name: 'UTEAM')
    }.to raise_exception(ActiveRecord::NotNullViolation)
  end

The test fails which is expected on one hand due to the semantics of create vs. create! but unexpected on the other as the exception is not supposed to be raised from the model validation (which is called on create/create!) but from the database saving the record.

However using create! will correctly throw an exception but it is a different one (higher up in the stack i presume):

  1) Ce::Team Team Model throwing not null exception if evaluation is missing in ctor
     Failure/Error:
       expect {
         Ce::Team.create!(name: 'UTEAM')
       }.to raise_exception(ActiveRecord::NotNullViolation)

       expected ActiveRecord::NotNullViolation, got
         #<ActiveRecord::RecordInvalid: Validation failed: Evaluation must exist> 

What is going on ? My first intuition is that there exists some layer within Rspec simulating the create (or save) in a different way, omitting the actual save.

count0
  • 2,537
  • 2
  • 22
  • 30

1 Answers1

1

I can't reproduce the problem. This is the output I get

$ RAILS_ENV=development bin/rails c
Running via Spring preloader in process 24202
Loading development environment (Rails 5.1.4)
2.4.0 :001 > team = Team.create(name: 'A-Team')
   (0.3ms)  BEGIN
   (0.3ms)  ROLLBACK
 => #<Team id: nil, name: "A-Team", created_at: nil, updated_at: nil, evaluations_id: nil> 
2.4.0 :002 > team.errors.full_messages
 => ["Evaluation must exist"] 
2.4.0 :003 > team = Team.create!(name: 'A-Team')
   (0.5ms)  BEGIN
   (0.5ms)  ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Evaluation must exist
    from (irb):3

As you can see the error is the same with create and create!. Try to use bin/rails instead of rails, I do suspect you are using 2 different versions of Rails.

Hoa
  • 3,179
  • 1
  • 25
  • 33
  • Thanks for trying to reproduce this. Are you using postgres ? As to my understanding the null constraint violation is supposed to be thown by the DB first, then catched by rails. I did work around the issue in rspec expecting RecordInvalid but it's addressing a different broader error type. – count0 Nov 27 '17 at 22:04
  • Yes, I'm using PostgreSQL. Validating a record before trying to save it in a database sounds more logical. So validation errors should be raised before DB errors. [Available callbacks - Creating an object](http://guides.rubyonrails.org/active_record_callbacks.html#creating-an-object) confirms my point. – Hoa Nov 28 '17 at 01:39
  • I agree that in practice there should be model validation before any db constraints. However the point was to test the DB constraint via RSpec. My console output with the was run with RAILS_ENV=test, without using spring and from within a Rails::Engine, not the main app. I will create a rails example app and upload it to github to reproduce this issue once i have some time. – count0 Nov 28 '17 at 15:30
  • 1
    @count0 You didn't clearly state in your questions that you want to test database constraints. To test database constraints, you can use [save](http://api.rubyonrails.org/classes/ActiveRecord/Validations.html#method-i-save) instead of `create` as it provides you with an option to bypass Rails validation. – Hoa Nov 29 '17 at 05:13