Rails 引擎主机应用程序 类 在 RSpec 测试中

Rails Engine host application classes in RSpec tests

我有一个 Rails 引擎,其中包含 link 某些主机应用程序 类 的方法(我知道这种类型的耦合很糟糕,但在这种情况下它是不可避免)。

我需要测试使用主机 类 的方法,但在尝试 double/mock/stub 主机 类 时出现 uninitialized constant MyEngine::BaseUser 错误(BaseUserTutor 在这种情况下)。

我曾试图通过创建模拟 类 来解决这个问题,但我认为我留下的是一个坏主意,这意味着我的测试不太有用(见下文)。

有什么想法可以做得更好,或者有更好的方向建议吗?

正如我上面所说,我是这样(糟糕地)解决这个问题的:

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

我正在测试的方法如下所示:

def host_full_name
    MyEngine.user_class.constantize.find(self.host_id).tutor.full_name
end

(MyEngine.user_class 是 "BaseUser")

您的引擎将所有内容命名为您的引擎。如果您正在尝试访问实际上在基本范围内定义的 class(即在您的引擎之外),您可以强制它在带有 :: 的基本范围内找到 class,例如:

 expect(ai.host_first_name).to eq ::BaseUser.find(1).tutor.first_name

我在问题中所做的似乎仍然是最好的解决方案,所以我继续用这种方法的另一个例子来回答这个问题。

这里我需要 mock/model 一个 Setting 在代码中这样使用的(在引擎之外):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