在 Ruby - Rails 项目中存根 .sample 使用 Il8n gem

Stubbing .sample in Ruby - Rails project using Il8n gem

我正在开发描述生成器功能,该功能将查看对象属性并通过从已设置的各种语言环境(多种语言)中提取字符串来填充描述。

我有以下代码

module Rooms
  class DescriptionGenerator
  attr_reader :room, :locale

  def initialize(room, locale="en")
    @room = room
    @locale = locale
  end

  private

  def t(key, options={})
    I18n.t("rooms.description_generator.#{key}", options.merge({ locale: locale }))
  end

  def wifi
    t("wifi", room_type: room.room_type.type).values.sample if room.wifi
  end

然后我有以下测试:

describe "#wifi" do
  let!(:room_with_wifi) { create(:room, :visible, wifi: true) }
  let!(:room) { create(:room) }

  #This one is failing
  it "returns wifi sentence if room has wifi" do
    sentence = I18n.t('rooms.description_generator.wifi', room_type: room_with_wifi.room_type.type).values.sample
    expect(Rooms::DescriptionGenerator.new(room_with_wifi,"en").send(:wifi)).to eq (sentence)
  end

  it "returns nil if room does not have wifi" do
    expect(Rooms::DescriptionGenerator.new(room,"en").send(:wifi)).to eq nil
  end
end

我的问题是"What is the best way to test this given the use of sample?"

我最初的解决方案(我很确定不推荐)是添加:

class DescriptionGenerator
  def sample(arr)
    if Rails.env.test?
      arr.values.first
    else
      arr.sample.values
    end
  end
end

'enforced' RSpec 将从我的语言环境中选择第一个选项,如下所示:

three_positive_reviews:
  a: "This is not so great"
  b: "This %{string_for_interpolation} is great."
  c: "This is bad"

这一切都过去了,直到我将要插入的字符串添加到测试中使用的第一个 (a) 中,并意识到在 i18n gem 中不支持数组内的插入(Interpolation in I18n array).

所以我重构为:

def t(key, options={})
  value = I18n.t("rooms.description_generator.#{key}")
  key = "#{key}.#{sample(value.keys)}" if value.is_a?(Hash)
  I18n.t("rooms.description_generator.#{key}", options.merge({ locale: locale }))
end

def sample(keys)
  if Rails.env.test?
    keys.first
  else
    keys.sample
  end
end

这现在通过了我的测试套件 - 但是在详细研究了这个之后想寻求更优化的解决方案以及我如何更适当地测试(可能通过存根样本??)而不使用行

if Rails.env.test?

我会执行以下操作(我没有 运行 代码,认为):

let(:room) { create(:room) }
let(:locale) { 'en' }
let(:generator) { Rooms::DescriptionGenerator.new(room, locale) } 

describe '#wifi' do
  let(:wifi_sentences) do
    I18n.t('rooms.description_generator.wifi', locale: locale)).values
  end

  context 'when the room has wifi' do
    it 'returns wifi sentence' do
      expect(generator.send(:wifi)).to be_in(wifi_sentences)
    end
  end

  context 'when the room has no wifi' do
    before do
      allow(room).to receive(:wifi).and_return(false) # or nil, don't know
    end

    it 'returns nil' do
      expect(generator.send(:wifi)).to be(nil)
    end
  end
end

希望这对您有所帮助:)

感谢 Victor 的回答 - 对重构我的测试非常有帮助。我想 post 提出我的最终解决方案,该解决方案考虑了您的建议并进行了一些调整,以防对其他人有用。

let(:room) { create(:room) }
let(:locale) { 'en' }
let(:generator) { Rooms::DescriptionGenerator.new(room, locale) }

describe "#laundry" do
  it "returns wifi sentence if room has wifi" do
    sentence = "This apartment is equipped with laundry facilities."
    room.laundry = true
    expect(generator.send(:laundry)).to eq(sentence)
  end

  it "returns nil if room is not have laundry" do
    expect(generator.send(:laundry)).to eq nil
  end
end

这适用于一个简单的单一选项语言环境:

  laundry: "This %{room_type} is equipped with laundry facilities."

然而,由于 Rails i18n 无法在数组中插入字符串(即当提供多个键值对时),我存根了示例。

context "translations with multiple options that require interpolation" do
  before do
    allow(generator).to receive(:sample) { |keys| keys.first }
  end

  describe "#three_positive_reviews" do

   context 'when the room has 3 positive reviews' do
     before do
       3.times do
         create(:feedback, :non_external, :positive, room: room)
       end
     end

    it "returns three_positive_reviews sentence" do
      sentence = "This apartment is highly reviewed"
      expect(generator.send(:three_positive_reviews)).to eq(sentence)
    end
  end

  context 'when the room does not have 3 positive reviews' do
    it "returns nil" do
      expect(generator.send(:three_positive_reviews)).to eq nil
    end
  end

然后能够将我的代码中的示例方法重构为:

def sample(keys)
  keys.sample
end

这对我来说似乎比我在问题中 post 的原始解决方案更简洁。