Mocking Net::SSH connections

Hi,

So I’m stuck again with creating a mock for Net::SSH, I’ve managed to
mock the call to Net::SSH.start and yield the Net::SSH mock but I am
totally stuck with mocking the session.shell.sync call and will also
need to mock the shell.send_command call also. Any help much
appreciated.

When I run the following spec I get the error:
Mock ‘Net::SSH’ received unexpected message :shell with (no args)

I’ve tried things like @shell = mock(Net::SSH::Service::Shell) but
this also gives an error complaining that Service doe snot exist.

test code

@connection = Ssh::Remote.new
@ssh = mock(Net::SSH)
Net::SSH.should_receive(:start).and_yield(@ssh)
Net::SSH.should_receive(:shell).and_return('something')

library code

require ‘net/ssh’

module Ssh
class Remote

def remote_command(server, user, commands=[])
  Net::SSH.start(server, user) do |session|
    shell = session.shell.sync
    commands.each do |command|
      out = shell.send_command command[:command]
    end
  end
end

end
end

On 21 Apr 2008, at 07:03, Jamie D wrote:

Net::SSH.should_receive(:shell).and_return(‘something’)

This should be @ssh.should_receive(:shell).and_return(‘something’)

But actually it will need to return a mock with :sync stubbed for the
line:
shell = session.shell.sync

Ashley


http://www.patchspace.co.uk/

On Mon, Apr 21, 2008 at 8:03 AM, Jamie D [email protected]
wrote:

Hi,

So I’m stuck again with creating a mock for Net::SSH, I’ve managed to
mock the call to Net::SSH.start and yield the Net::SSH mock but I am
totally stuck with mocking the session.shell.sync call and will also
need to mock the shell.send_command call also. Any help much
appreciated.

Don’t mock APIs you don’t own. http://rubyurl.com/53Y6

Instead - create a thin API around such APIs and have your app use
that instead. Now you have an easily mockable API.
Also make sure you have end-to-end tests that use Net::SSH without any
mocking.

Aslak

On Mon, Apr 21, 2008 at 8:03 AM, Jamie D [email protected]
wrote:

Hi,

So I’m stuck again with creating a mock for Net::SSH, I’ve managed to
mock the call to Net::SSH.start and yield the Net::SSH mock but I am
totally stuck with mocking the session.shell.sync call and will also
need to mock the shell.send_command call also. Any help much
appreciated.

Don’t mock APIs you don’t own. http://rubyurl.com/53Y6

Instead - create a thin API around such APIs and have your app use
that instead. Now you have an easily mockable API.
Also make sure you have end-to-end tests that use Net::SSH without any
mocking.

Aslak

Thanks for the info Aslak, I have rewritten my code like you suggest
but have one issue with raising an exception. When I try the code in
the console an exception is raised correctly but in my test code I get
a message “command did not return expected result”

test

describe Ssh, “Remote” do

before(:each) do
@ssh = mock(Net::SSH)
@response = mock(Net::SSH)
@connection = Ssh::Remote.new
@connection.should_receive(:start).with(‘server’,
‘root’).and_return(@ssh)
@connection.should_receive(:close)
@ssh.should_receive(:send_command).with(‘ls
/’).and_return(@response)
@response.should_receive(:stdout).and_return(‘a list’)
end

it “should raise exception if response does not match expected” do
@connection.remote_command(‘server’, ‘root’, [{:command => ‘ls /’,
:expects => /blah/}]).
should raise_error(Ssh::CommandError, ‘command did not return
expected result’)
end

end

code exception.rb

module Ssh
class CommandError < Exception
end
end

ssh.rb

require ‘net/ssh’
require ‘ssh/exception’

module Ssh
class Remote

def remote_command(server, user, commands=[])
  shell = start(server, user)
  commands.each do |command|
    out = shell.send_command command[:command]
    unless out.stdout =~ command[:expects] or 

command[:expects].blank?
close
raise(CommandError.new, ‘command did not return expected
result’)
end
end
close
return true
end

def start(server, user)
  @session = Net::SSH.start(server, user)
  @session.shell.sync
end

def close()
  @session.close
end

end
end

Thanks
Jamie

Fixed it, I was missing the lambda:

it “should raise exception if response does not match expected” do
lambda{ @connection.remote_command(‘server’, ‘root’, [{:command =>
‘ls /’, :expects => /blah/}]) }.
should raise_error(Ssh::CommandError, ‘command did not return
expected result’)
end