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 anexec
request.channel.gets_data "..."
: Scripts the server sending data back to the client on the channel'sstdout
.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