5

How would be the correct way to mock or override the Kernel.system method so that when called with:

system("some command")

instead of executing the command, it executes some predefined code?

I tried adding the following to my Test class:

module Kernel
    def system
        puts "SYSTEM CALL!!"
    end
end

But it did not work as expected, instead, the system call was run when executing a test.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
obaqueiro
  • 982
  • 12
  • 29

4 Answers4

14

In some cases doing expect(Kernel).to receive(:system) is not enough.

Consider this example:

foo_component.rb

class FooComponent
  def run
    system('....')
  end
end

foo_component_spec.rb

require 'spec_helper'

describe FooComponent do
  let(:foo_component) { described_class.new }

  describe '#run' do
    it 'does some awesome things' do
      expect(Kernel).to receive(:system).with('....')
      foo_component.run
    end
  end
end

It will not work. This is because Kernel is a module and Object (parent class) is mixes in the Kernel module, making all Kernel method available in "global" scope.

This is why proper tests should looks like this:

require 'spec_helper'

describe FooComponent do
  let(:foo_component) { described_class.new }

  describe '#run' do
    it 'does some awesome things' do
      expect(foo_component).to receive(:system).with('....')
      foo_component.run
    end
  end
end
Artur Małecki
  • 1,426
  • 16
  • 14
7

If you are talking about unit tests and use Rspec, you should be able to do it like this:

Kernel.should_receive(:system)

or a little more loose:

Kernel.stub(:system)

More info: https://www.relishapp.com/rspec/rspec-mocks/v/2-13/docs/message-expectations/expect-a-message

Spajus
  • 7,356
  • 2
  • 25
  • 26
  • 1
    The link I gave you was from the official rspec docs: https://www.relishapp.com/rspec/. You can also check out http://betterspecs.org/ - it gives a number of best practices on how to use rspec properly. – Spajus May 20 '13 at 18:31
  • Yes, I am talking about Unit tests, but unfortunately the code is not using rspec but simple Test Unit – obaqueiro May 20 '13 at 18:44
  • 1
    In that case you should take a look at: https://github.com/freerange/mocha - `Kernel.expects(:system).returns(true)` – Spajus May 20 '13 at 18:54
  • Does the `should_receive(:method_name)` stub the method so that it isn't executed? Or do you need to use `.stub(:method_name)` to prevent it from being executed? – Automatico Mar 04 '14 at 15:02
5

If it is within a class, kernel is mixed in. So you would just mock it as if it was part of the object.

E.g.

expect(subject).to receive(:system).and_return(foo)
John C.
  • 63
  • 1
  • 6
4

Since this question was asked, RSpec 3 has come out with a new syntax, where you would write this:

expect(Kernel).to receive(:system)

If your code checks whether or not the system call was a success, you can specify the result like this:

expect(Kernel).to receive(:system).and_return(true)

The loose version:

allow(Kernel).to receive(:system).and_return(true)
Pete
  • 12,206
  • 8
  • 54
  • 70
  • 2
    FYI Using ruby 2.3.0 and rspec-core 3.4.2, I had to use `Kernel.system(...)` instead of just `system(...)` for the mock to pass. – Dennis Feb 17 '16 at 15:33