Search code examples
ruby-on-railsrubytestingrspecrspec-rails

Why is Application Record changing my RSpec Test result


Reasonably green to testing but I was following along with a simple udemy course. I used the RSpec documentation to set up RSpec in rails to try out some testing. But I have come across an issue that for the life of me I can't figure out...

require "rails_helper"

RSpec.describe User, type: :model do
  subject { described_class.new("John") }

  it "initializes a name" do
    expect(subject.name).to eq("John")
  end

  context "with no argument" do
    subject { described_class.new }

    it "should default to Bill as the name" do
      expect(subject.name).to eq("Bill")
    end
  end
end

# This is my test code. 

# This is my User model. 

class User < ApplicationRecord
  attr_reader :name

  def initialize(name = "Bill")
    @name = name
  end
end

When I run the test it fails and says that the second test isn't returning Bill but 'nil'. However, in my User model if I remove the < Application Record it passes... Also, if I add a second parameter in the initialize, it randomly passes the default test and fails the first one returning the default name... I'm completely confused as I have been learning the testing without ApplicationRecord and that seems to be the part where it is failing. I have tried changing the subject to let(:testing){User.new} but that doesn't work. Any help seriously appreciated here as I can't seem to find it through google.

To let you know I have gem 'rspec-rails', '~> 4.0.0' included in my GemFile in the :development, :test part.


Solution

  • You are trying to override default initializer of a model and you doing it the wrong way. When you call new on ActiveRecord class you need to pass a hash of parameters. To have name field in a model you need to define it in DB schema.

    Creation of an instance of User for the first test case should look like this:

    described_class.new(name: "John")
    

    I see these ways to have a default value for an attribute:

    Set it using a callback

    class User < ApplicationRecord
      after_initialize :set_name
    
      private
    
      def set_name
        self.name ||= 'Bill' # Set name to Bill if it is nil
      end
    end
    
    

    Override initialize method.

    # Please avoid this approach
    class User < ApplicationRecord
      def initialize(*args)
        super # This will initiate default behaviour of a model
        self.name ||= 'Bill' 
      end
    end
    

    Using attributes API as @engineersmnky suggested

    class User < ApplicationRecord
      attribute :name, :string, default: 'Bill'
    end
    

    I strongly recommend using a callback or attributes API approach to not broke default behaviour.

    After that, your tests should pass I believe.