NS Raghuwansi
NS Raghuwansi

Reputation: 1

How to write an rspec for do-end block and check method is called or not using stub/mock

All the unit test cases should be mocked/stubbed. Rspec for checkerror method and result method.

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 detected\n")
      puts "#{@check.checker.file_path} :: No offensenses detected\n"
    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

I have written rspec for checkerror method

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

but getting error

TypeError: no implicit conversion of nil into string.

I think haven't entered into loop of do-end block and stubbing is also syntacticly wrong I guess.

Upvotes: 0

Views: 254

Answers (1)

Tom Lord
Tom Lord

Reputation: 28305

Here is a fairly minimal change to your code, so that it makes a little bit more sense and can actually be tested:

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 detected\n")
      puts "#{file} :: No offensenses detected\n"
    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

Key notes:

  • You need to actually set the instance variables. In your previous implementation, for example, @path was nil - so that didn't make any sense.
  • Stubbing variables like @checks_to_run (which you mis-spelt, by the way!) isn't a great idea. Why not just set the variable to the desired value, in the first place?
  • You can't stub variables that aren't even initialised, like @check -- that's not how stubbing works. Stubbing isn't like "spooky action at a distance", modifying how the code actually behaves; it's about setting response behaviour of dependencies.

So with that said... Here's roughly what your test could now look like:

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

Upvotes: 1

Related Questions