Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I test methods in the ApplicationRecord abstract base class?

I haven't found a good way to test ApplicationRecord methods.

Let's say I have a simple method named one:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def one
    1
  end
end

And I want to test it:

describe ApplicationRecord do
  let(:it) { described_class.new }

  it 'works' do
    expect(it.one).to eq 1
  end
end

This dies, unsurprisingly, with NotImplementedError: ApplicationRecord is an abstract class and cannot be instantiated.

So I tried the anonymous class suggestion in Testing abstract classes in Rspec:

let(:it) { Class.new(described_class).new }

And this dies with TypeError: no implicit conversion of nil into String, presumably because the record's table name is nil.

Can anyone suggest a nice, simple way to test ApplicationRecord methods? Hopefully one that doesn't introduce dependencies on other classes in my application and doesn't root around in ActiveRecord internals?

like image 514
bronson Avatar asked Feb 12 '16 22:02

bronson


People also ask

What is ActiveRecord base?

ActiveRecord::Base indicates that the ActiveRecord class or module has a static inner class called Base that you're extending.

What is abstract_ class Rails?

Abstract Base Classes An abstract base class in Rails Model is simply a model that is not persistent, i.e not backed by a table. It would look like: # app/models/citizen.rbclass Citizen < ApplicationRecord. self.abstract_class = true. end.

What is an abstract class in Ruby?

An abstract class is created with all of the methods you'd expect to find on ApplicationRecord — validators, persistence methods, etc — but an instance of an abstract class can never exist on its own.

What is Rails ApplicationRecord?

ApplicationRecord inherits from ActiveRecord::Base , which defines a number of helpful methods. You can use the ActiveRecord::Base.table_name= method to specify the table name that should be used: class Product < ApplicationRecord self. table_name = "my_products" end Copy.


2 Answers

This has been working for me in our tests:

class TestClass < ApplicationRecord
  def self.load_schema!
    @columns_hash = {}
  end
end

describe ApplicationRecord do
  let(:record) { TestClass.new }

  describe "#saved_new_record?" do
    subject { record.saved_new_record? }

    before { allow(record).to receive(:saved_change_to_id?).and_return(id_changed) }

    context "saved_change_to_id? = true" do
      let(:id_changed) { true }

      it { is_expected.to be true }
    end

    context "saved_change_to_id? = false" do
      let(:id_changed) { false }

      it { is_expected.to be false }
    end
  end
end

It just prevents the class from attempting a database connection to load the table schema.

Obviously as Rails moves along you might have to update the way you do this but at least it is located in one easy to find place.

I prefer this much more than having another module just to allow testing.

like image 59
ozzyaaron Avatar answered Sep 26 '22 02:09

ozzyaaron


I would suggest extracting those methods into module (concern) and leave ApplicationRecord alone.

module SomeCommonModelMethods
  extend ActiveSupport::Concern

  def one
    1
  end
end

class ApplicationRecord < ActiveRecord::Base
  include SomeCommonModelMethods
  self.abstract_class = true
end

describe SomeCommonModelMethods do
  let(:it) { Class.new { include SomeCommonModelMethods }.new } } 

  it 'works' do
    expect(it.one).to eq 1
  end
end
like image 22
Oleg Antonyan Avatar answered Sep 24 '22 02:09

Oleg Antonyan