Ruby中的RSpec和对象初始化

Uzz*_*zar 2 ruby rspec ruby-on-rails

有些东西告诉我,我在测试中缺少一个关键的概念/想法,或者(天堂禁止)ruby如何初始化对象.

我有一个类方法接受两个参数并返回所述类的实例.所以它看起来像这样:

    class Manager

      def self.run(first_arg, second_arg)
        new(first_arg, second_arg)
      end
    end
Run Code Online (Sandbox Code Playgroud)

这是我的RSpec测试:

    RSpec.describe Manager, type: :api do

      let(:first_arg) { FactoryGirl.build_stubbed(:first_arg) }
      let(:second_arg) { AccountMailer.new }

      describe '::run' do 
        it "accepts two arguments" do 
          expect(Manager).to receive(:run).with(first_arg, second_arg)
          Manager.run(first_arg, second_arg)
        end

        it "instantiates the class with 2 arguments" do 
          expect(Manager).to receive(:new).with(first_arg, second_arg)
          Manager.run(first_arg, second_arg)
        end
      end
    end
Run Code Online (Sandbox Code Playgroud)

由于(我相信)该方法:initialize被new调用,我将代码更新为:

   class Manager 
     # add attr_reader for read access 
     attr_reader :first_arg, :second_arg 

     def initialize(first_arg, second_arg)
       @first_arg = first_arg
       @second_arg = second_arg
     end

     def self.run(first_arg, second_arg)
       new(first_arg, second_arg)
     end
   end
Run Code Online (Sandbox Code Playgroud)

我的测试失败并返回此错误:

    1) Manager#run instantiates the class
       Failure/Error: expect(Manager).to receive(:new).with(first_arg, second_arg)
       Wrong number of arguments. Expected 0, got 2.
Run Code Online (Sandbox Code Playgroud)

我的主要问题是:

为什么看起来我传递给初始化的方法没有在rspec中被选中?我希望测试能够通过,因为Manager.new,initialize如果没有在类中定义,如果没有传递2个参数,则会失败.

谁能指出我在这里缺少什么?感谢您的反馈.谢谢.

Lau*_*nen 8

I.我能用这样的代码重新创建你的问题

class Manager
  def self.run(a, b)
    new(a, b)
  end
end

RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end

RSpec.describe Manager, type: :api do
  let(:a) { 1 }
  let(:b) { 2 }

  describe '::run' do
    it 'instantiates the class with 2 arguments' do
      expect(Manager).to receive(:new).with(a, b)
      Manager.run(a, b)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

结果如下:

1) Manager#run instantiates the class with 2 arguments
   Failure/Error: expect(Manager).to receive(:new).with(a, b)
     Wrong number of arguments. Expected 0, got 2.
Run Code Online (Sandbox Code Playgroud)

这是因为验证功能.启用此设置(应该是)时,RSpec将确保该对象实现了被存根/模拟的接口.在这种情况下,RSpec会在该行上抛出一个错误expect(Manager).to receive(:new).with(a, b),因为它实际上会查看Manager该类并检查initialize是否可以使用2个参数.

如果您将管理器更改为如下所示,则示例将通过:

class Manager
  attr_reader :a, :b

  def initialize(a, b)
    @a = a
    @b = b
  end

  def self.run(a, b)
    new(a, b)
  end
end
Run Code Online (Sandbox Code Playgroud)

II.但是你真的不需要使用模拟来实现这样的功能.如果您只是检查是否返回了正确类型的实例,那么最好只看真实的实例.

RSpec.describe Manager, type: :api do
  let(:a) { 1 }
  let(:b) { 2 }

  describe '::run' do
    subject { described_class.run(a, b) }

    it 'instantiates the class with 2 arguments' do
      expect(subject).to be_an_instance_of(Manager)
    end

    it 'sets a to the first argument' do
      expect(subject.a).to eq(a)
    end

    it 'sets b to the second argument' do
      expect(subject.b).to eq(b)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

III.在这个例子中:

expect(Manager).to receive(:run).with(first_arg, second_arg)
Manager.run(first_arg, second_arg)
Run Code Online (Sandbox Code Playgroud)

您设置了一个断言,然后立即调用代码来传递该断言.所以没有真正测试任何东西.

正确模拟/存根是相当先进的测试概念,很容易弄错,所以如果你可以不用它,只是不用它,它会使事情变得更容易.

如果您想了解更多有关测试/何时模拟的信息.我推荐Sandi Metz的演讲.https://www.youtube.com/watch?v=URSWYvyc42M