2013-06-25 4 views
0

Я новичок в stubbing/mocking.Как заглушить или издеваться сторонняя библиотека

Как я могу заглушить методы из внешней библиотеки, поэтому я могу только проверять методы моего модуля, фактически не звонив в библиотеку?

Кроме того, мне интересно, мой подход к написанию этого модуля - путь, который может нарушить какой-то важный принцип программирования?

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

    @ftp = nil 

    def self.login  
    if [email protected] || @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 

ответ

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 [email protected] || @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 

, когда вы более комфортно с тем, что происходит.

+1

Подход gmacdokugall - хороший, но существует коварная проблема с кодом, который может вызвать разочарование: переменная '@ ftp', назначенная в строке 6 файла' file_module.rb', не совпадает с '@ ftp', на который ссылаются методы экземпляра. 'self', когда' @ ftp' присваивается, является ** модулем **, а 'self' в методах экземпляра является экземпляром ** **, поэтому упоминаются две разные переменные' @ ftp'. Одним из решений было бы иметь метод экземпляра «getter» для 'ftp', который присваивает' @ ftp', если он еще не определен. –

+0

Я всегда думал, что инъекция зависимости просто для тестирования - это запах кода. –