Testing with Net::SSH::Test

Net::SSH includes a powerful testing framework that allows you to script and mock SSH interactions. This is invaluable for writing unit tests for code that depends on Net::SSH, without needing a live SSH server.

The core of the framework is the Net::SSH::Test module.

Getting Started

To use the testing framework, include the Net::SSH::Test module in your test class.

require 'minitest/autorun'
require 'net/ssh/test'

class MySshClientTest < Minitest::Test
  include Net::SSH::Test

  # ... your tests go here
end

This gives you access to three key methods: story, connection, and assert_scripted.

The story Block

The story block is where you define the expected sequence of events in an SSH session. You script both what your client will send (sends_*) and what the mock server will send back (gets_*).

story do |session|
  # 'session' represents the mock server side
  channel = session.opens_channel
  channel.sends_exec "ls -l"
  channel.gets_data "total 0\n-rw-r--r-- 1 user group 0 Jan 1 12:00 file.txt\n"
  channel.gets_close
  channel.sends_close
end
  • session.opens_channel: Scripts the client opening a channel and the server confirming it.
  • channel.sends_exec "...": Scripts the client sending an exec request.
  • channel.gets_data "...": Scripts the server sending data back to the client on the channel's stdout.
  • channel.gets_close: Scripts the server closing the channel.
  • channel.sends_close: Scripts the client acknowledging the close.

The assert_scripted Block

After defining your story, you run your actual client code inside an assert_scripted block. This block executes your code against a mocked Net::SSH connection that will follow the script you defined.

The assertion succeeds if and only if your code performs exactly the actions described in the story, in the same order.

assert_scripted do
  # 'connection' is a mock Net::SSH::Connection::Session object
  # provided by Net::SSH::Test
  output = connection.exec!('ls -l')
  assert_equal "total 0\n-rw-r--r-- 1 user group 0 Jan 1 12:00 file.txt\n", output
end

Complete Example

Here's a full test case demonstrating how to test a simple method that uses Net::SSH.

require 'minitest/autorun'
require 'net/ssh/test'

# The class we want to test
class RemoteFileChecker
  def initialize(connection)
    @ssh = connection
  end

  def file_exists?(path)
    output = @ssh.exec!("test -f #{path} && echo 'exists'")
    output.strip == 'exists'
  end
end

# The test case
class TestRemoteFileChecker < Minitest::Test
  include Net::SSH::Test

  def test_file_exists_should_return_true_when_file_is_present
    # 1. Define the expected SSH interaction
    story do |session|
      channel = session.opens_channel
      channel.sends_exec "test -f /path/to/file.txt && echo 'exists'"
      channel.gets_data "exists\n"
      channel.gets_close
      channel.sends_close
    end

    # 2. Run the code under test and assert the script was followed
    assert_scripted do
      # 'connection' is the mock Net::SSH session
      checker = RemoteFileChecker.new(connection)
      assert checker.file_exists?('/path/to/file.txt')
    end
  end

  def test_file_exists_should_return_false_when_file_is_absent
    story do |session|
      channel = session.opens_channel
      channel.sends_exec "test -f /path/to/nonexistent.txt && echo 'exists'"
      channel.gets_data ""
      channel.gets_close
      channel.sends_close
    end

    assert_scripted do
      checker = RemoteFileChecker.new(connection)
      assert !checker.file_exists?('/path/to/nonexistent.txt')
    end
  end
end