Search code examples
ruby-on-railstestingrspecrails-engines

Rails Engine host application classes in RSpec tests


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")


Solution

  • 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