如何存根或模拟第三方库

时间:2013-06-25 03:19:16

标签: ruby-on-rails ruby rspec

我是捣蛋/嘲笑的新手。

如何从外部库中存根方法,这样我只能在不实际调用库的情况下测试模块的方法?

另外,我想知道,我的方法是编写这个模块的方法还是违反了一些重要的编程原则?

# file_module.rb
module FileModule
  require 'net/ftp'

  @ftp = nil

  def self.login    
    if !@ftp || @ftp.closed?
      @ftp = Net::FTP.new(Rails.configuration.nielsen_ftp_server)
      @ftp.login(Rails.configuration.nielsen_ftp_user, Rails.configuration.nielsen_ftp_password)
    end
  end

  def self.get_list_of_files_in_directory(directory, type)
    login
    @ftp.chdir("/#{directory}")        
    files = case type
      when "all"            then @ftp.nlst("*")
      when "add"            then @ftp.nlst("*add*")
    end
  end
end

# file_module_spec.rb (RSpec)    
require 'spec_helper'
describe NielsenFileModule do
  describe ".get_list_of_files_in_directory" do
    it "returns correct files for type all" do
      # how to mock Net::FTP or stub all its methods so I simulate the return value of @ftp.nlst("*")?
      NielsenFileModule.get_list_of_files_in_directory("test_folder", "all").count.should eq 6
    end
  end
end  

1 个答案:

答案 0 :(得分:2)

最简单的方法是使用Dependency Injection的原则。您可以将任何外部依赖项传递给您正在测试的类。在这种情况下是@ftp对象。

您在对象上使用成员变量以及类(或静态)方法时会出现一个错误。

考虑修改您的课程以执行以下操作:

# file_module.rb
module FileModule
  require 'net/ftp'
  attr_accessor :ftp

  @ftp = Net::FTP.new(Rails.configuration.nielsen_ftp_server)

  def login    
    if !@ftp || @ftp.closed?
      @ftp.login(Rails.configuration.nielsen_ftp_user, Rails.configuration.nielsen_ftp_password)
    end
  end

  def get_list_of_files_in_directory(directory, type)
    login
    @ftp.chdir("/#{directory}")        
    files = case type
      when "all"            then @ftp.nlst("*")
      when "add"            then @ftp.nlst("*add*")
    end
  end
end

现在在测试中,您可以测试模块上的对象方法,而不是测试模块上的类方法。

require 'spec_helper'
class FileClass
  include FileModule
end

let(:dummy) { FileClass.new }
let(:net_ftp) { double(Net::FTP) }
before { dummy.ftp = net_ftp }

describe FileModule do
  describe '.login' do
    context 'when ftp is not closed' do
      before { net_ftp.stub(:closed) { true } }
      it 'should log in' do
        net_ftp.should_receive(:login).once
        dummy.login
      end
    end
  end
end

现在你可以在你的net_ftp对象上存根或设置期望,如上所示。

注意:有很多方法可以做到这一点,但这是一个很有意义的好例子。您正在将外部服务提取为可以加倍的内容,并替换为模拟功能。

您还可以删除类方法并执行以下操作:

Net::FTP.any_instance.stub

当你对正在发生的事情感到更加自在时。