Reputation: 599
I have a Rails Engine that includes methods that link to some of the host application's classes (I know this type of coupling is bad, but in this case it is unavoidable).
I need to test methods that use the host's classes, but I get a uninitialized constant MyEngine::BaseUser
error when trying to double/mock/stub the host's classes (BaseUser
or Tutor
in this case).
I have had a stab at getting round this problem by creating mock classes, but I think what I've left with is a bad idea and means my tests are less useful (see below).
Any idea what I could do better, or suggestions for a better direction to go in?
As I said above, I got round this (badly) like this:
BaseUser = Class.new do
attr_accessor :id
def initialize(id = 1)
@id = id
end
def self.find(id)
self.new(id)
end
def tutor
Tutor.find(self.id)
end
end
class Tutor
attr_accessor :id, :first_name
def initialize(id = 1)
@id = id
@first_name = "Tutor with ID #{id}'s first name"
end
def self.find(id)
self.new(id)
end
end
it 'returns the hosts first name' do
allow(MyEngine).to receive_message_chain(:user_class, :constantize) { BaseUser }
ai = FactoryGirl.create(:availability_interval, host_id: 1)
expect(ai.host_first_name).to eq BaseUser.find(1).tutor.first_name
end
The method I am testing looks like this:
def host_full_name
MyEngine.user_class.constantize.find(self.host_id).tutor.full_name
end
(MyEngine.user_class
is "BaseUser")
Upvotes: 0
Views: 285
Reputation: 599
What I did in the question still seems to be the best solution, so I am going ahead and answering this question with another example of this approach.
Here I needed to mock/model a Setting
that was used like this in the code (outside of the Engine): my_value = Setting.find_by_name('SETTING_NAME').value
describe 'ratable_based_on_time' do
Setting = Class.new do
def self.value
end
def self.find_by_name(name)
end
end
before(:each) do
allow(Setting).to receive_message_chain(:find_by_name, :value).and_return("30")
end
let(:availability_interval) { FactoryGirl.create(:availability_interval) }
it 'should return availability_intervals that are rateable based on time since start' do
availability_interval.update_column(:start_time, 1.hour.ago)
availability_interval.reload
expect(AvailabilityInterval.ratable_based_on_time).to eq [availability_interval]
end
it 'should not return availability_intervals that are not rateable based on time since start' do
availability_interval.update_column(:start_time, 25.minutes.ago)
availability_interval.reload
expect(AvailabilityInterval.ratable_based_on_time).to eq []
end
end
Upvotes: 0
Reputation: 27747
Your engine namespaces everything to your engine. If you are trying access a class that's actually defined in the base scope (ie outside of your engine), you can force it to find that class in the base scope with ::
eg:
expect(ai.host_first_name).to eq ::BaseUser.find(1).tutor.first_name
Upvotes: 1