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

#ruby #rspec

Вопрос:

Все примеры модульных тестов должны быть высмеяны/заглушены. Rspec для метода проверки ошибок и метода результата.

 require 'yaml'
require_relative 'checkerror'

class Operations
  def initialize
      @path
      @checks_to_run
      @check
  end

  # This will prints the result of each file offences or no offences
  def result (result_log: File.new('result.txt', 'a '))
    # @check.errors should be stubbed with a value to enter in if or else block
    if @check.errors.empty?
      # Output is printing in both console as well as file
      result_log.write("#{@check.checker.file_path} :: No offensenses detectedn")
      puts "#{@check.checker.file_path} :: No offensenses detectedn"
    else
      @check.errors.uniq.each do |err|
        puts "#{@check.checker.file_path} : #{err}n"
        result_log.write("#{@check.checker.file_path} : #{err}n")
      end
    end
    result_log.close
  end

  def rules_to_run
    @checks_to_run = YAML.load(File.read('lib/property.yaml'))
  end

  def path_of_directory
    @path = gets.chomp
  end

  def checkerror
    Dir[File.join(@path, '**/*.rb')].each do |file|
      @check = CheckError.new(file)
      @check.check_alphabetized_constants if @checks_to_run.include?('check_alphabetized_constants')
      @check.check_empty_line_before_return if @checks_to_run.include?('check_empty_line_before_return')
      #result is called to print errors
      result
    end
  end
end
 

Я написал rspec для метода проверки ошибок

 context '#checkerror' do
  it 'only check the method is called or not' do
    allow(@check_to_run).to receive(:include?).and_return(true)
    allow(@check).to receive(:check_alphabetized_contants)
    operation = Operation.new
    operation.checkerror
    expect(@check).to have_received(:check_alphabetized_constants)
  end
end
 

но получаю ошибку

 TypeError: no implicit conversion of nil into string.
 

Я думаю, что я не вошел в цикл конечного блока, и заглушка также синтаксически неверна, я думаю.

Комментарии:

1. Пожалуйста, отредактируйте свой вопрос, чтобы добавить полную трассировку стека и любые инициализаторы rspec, которые позволили бы воспроизвести вашу ошибку.

2. Это довольно неприятный вопрос, потому что вы просите о довольно конкретной помощи в реализации rspec, когда базовый код на самом деле не имеет смысла. Как я уже упоминал в предыдущем случае, когда вы опубликовали это, вы @path @check_to_run на самом деле ни для чего не устанавливаете переменные или, поэтому этот код на самом деле недействителен; что вы ожидаете Dir[File.join(nil, '**/*.rb')].each .... сделать?!

3. Другими словами, вы не можете этого сделать: operation = Operation.new . Ваш Operation класс, по крайней мере, ожидает, что он будет инициализирован с path помощью «и checks_to_run «. Я попытаюсь переписать это для вас в ответе, но вам действительно нужно сделать шаг назад и написать правильную реализацию, прежде чем приступать к ее модульному тестированию.

4. Конечно. Я сделаю

Ответ №1:

Вот довольно минимальное изменение в вашем коде, чтобы оно имело немного больше смысла и действительно могло быть протестировано:

 require 'yaml'
require_relative 'checkerror'

class Operations
  def initialize(path: path_of_directory, checks_to_run: rules_to_run):                   
    @path = path      
    @checks_to_run = checks_to_run
  end                 
                      
  def log_result(file, errors, result_log: File.new('result.txt', 'a '))
    if errors.empty?
      # Output is printing in both console as well as file
      result_log.write("#{file} :: No offensenses detectedn")
      puts "#{file} :: No offensenses detectedn"
    else                              
      errors.uniq.each do |err|
        puts "#{file} : #{err}n"
        result_log.write("#{file} : #{err}n")
      end                             
    end                               
    result_log.close                  
  end                                 
                                      
  def checkerror                      
    Dir[File.join(@path, '**/*.rb')].each do |file|
      checker = CheckError.new(file)
      checker.check_alphabetized_constants if @checks_to_run.include?('check_alphabetized_constants')
      checker.check_empty_line_before_return if @checks_to_run.include?('check_empty_line_before_return')
      log_result(file, checker.errors)
    end         
  end           
                
  # These methods feel very out-of-place!! It would be advisiable to move these outside of this class...
  def rules_to_run
    @checks_to_run = YAML.load(File.read('lib/property.yaml'))
  end           
                
  def path_of_directory
    @path = gets.chomp
  end           
end
 

Основные примечания:

  • Вам нужно фактически задать переменные экземпляра. В вашей предыдущей реализации, например, @path было nil — так что это не имело никакого смысла.
  • Заглушать переменные типа @checks_to_run (которые вы, кстати, неправильно написали!) -не лучшая идея. Почему бы просто не установить переменную на желаемое значение, в первую очередь?
  • Вы не можете заглушать переменные, которые даже не инициализированы, например @check … так не работает заглушка. Заглушка не похожа на «жуткое действие на расстоянии», изменяющее то, как на самом деле ведет себя код; речь идет о настройке поведения зависимостей в ответ.

Итак, с учетом сказанного… Вот примерно как теперь может выглядеть ваш тест:

 describe '#checkerror' do
  let(:path) { Dir.mktmpdir('my-temporary-directory') }
  let!(:rb_file_in_path) { Tempfile.new('test-file.rb', tmpdir) }
  let(:mock_checker) { instance_double(CheckError) }

  it 'calls check_alphabetized_contants when this is a check to run' do
    allow(CheckError).to receive(:new).and_return(mock_checker)
    allow(mock_checker).to receive(:check_alphabetized_constants)

    operation = Operation.new(path: path, checks_to_run: ['check_alphabetized_constants'])
    operation.checkerror

    expect(mock_checker).to have_received(:check_alphabetized_constants)
  end
end