zhisme
zhisme

Reputation: 2800

Proper way to mock ruby-core classes

I want to know whether this solution is community approved by mocking object in ruby. If not please describe why, and how can I improve design of code or maybe test.

Consider I have the following code.

lib/config_loader.rb

class ConfigLoader
  HOME_DIR = '~/my/home_dir'.freeze
  ...

  def load(path)
    path = File.expand_path(path)

    if File.exist?(path)
      File.read(path) 
    else
      File.read(HOME_DIR)
    end
  end
end

When testing I actually do not want anything to be created under path that I defined in variable, so I just want to mock this behaviour

spec/lib/config_loader_spec.rb

RSpec.describe ConfigLoader do
  describe '.load' do
    subject { described.class.new.load(path) }

    let(:path) { 'path/that/should/exist' }

    context 'when path' do
      before do 
        allow(File).to receive(:exist?).with(path).and_return(true)
        allow(File).to receive(:read).with(path).and_return('my content')
      end

      it { expect { subject }.to_not raise_error { Errno::ENOENT }
    end
  end
end

maybe should I do some class_double of File Class. I'm not sure about the way I provided, so I need some info how to do it in common/best-practices way

Upvotes: 0

Views: 249

Answers (1)

Jörg W Mittag
Jörg W Mittag

Reputation: 369420

One of the fundamental tenets of Test-Driven / Behavior-Driven Development/Design is Don't Mock What You Don't Own (coined in the book Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce).

Your example is violating this tenet: you don't own File, therefore you should not mock it.

Instead, what you could do is create your own abstraction for interacting with the file system, which only has the functionality you are actually requiring. Then you can create two implementations of this abstraction: one that uses Ruby's File class, and a mock one that does nothing. If you want to get fancy, you can even create one that simulates a file system in memory.

Of course, you have now just pushed the problem around: you now have untested code in your file abstraction implementation. However, ideally, this code should be "almost" trivial. And obviously, you can still have an integration test both for your file abstraction implementation, and also an integration test for ConfigLoader that uses the real implementation instead of the mock or the simulation.

Here's some further reading if you are interested:

Upvotes: 2

Related Questions