3

I've written a really simple date server:

require 'socket'

s = TCPServer.new 3939
  while (conn = s.accept)
    Thread.new(conn) do |c|
      c.print "Enter your name: "
      name = c.gets.chomp
      c.puts "Hi #{name}, the date is..."
      c.print `date`
      c.close
    end
  end

A user connects, a thread is spawned, they enter their name, the date is returned. Simple.

I'm wondering about how I would go abouts testing something like this in rspec. Some ideas that I've had: 1.) Use VCR to record a server connection and Timecop to freeze and return a date. 2.) Connect to the actual server in a before block. I'm not entirely sure how to do this as when I run rspec, I think it actually runs the server...or something happens that the terminal just kind of freezes and waits for something... example test code:

before
  @server = TCPSever.new 3939
end

it "does something.."
  conn = @server.accept
  # etc
end

after
  @server.close
end

3.) Stub the connection. 4.) Don't even try to test that threads are created, and just put the name request and date response into a method and test this.

I looked at puma's tests to see how they test threads: https://github.com/puma/puma/blob/master/test/test_puma_server.rb#L27

I'd really appreciate some help with this. It's just a simple exercise where I'd like to see how best to implement some tests for this kind of server. Like, simulating a user connecting and entering their name. Thank you in advance.

ssks
  • 81
  • 5

1 Answers1

2

I would consider organizing the server as a class with this signature:

class DateServer
  # Initialize the server.  If port is 0, find
  # an unused port and use that.
  def initialize(port = 0)
  def start
  def stop
  def port    # return the bound port
end

Your test then sets up and tears down the server:

before do
  @server = Server.new
  @server.start
end

after do
  @server.stop
end

Since this server has no side-effects, your test will connect to it, send some stuff, and get some stuff back:

it "prints the date" do
  @socket = TCPSocket.new("localhost", @server.port)
  @socket.puts "Fred"
  @socket.close_write
  output = @socket.read
  expect(output).to match /Hi Fred/
end

To check that the output contains the date, either use a regular expression. Here's an untested and probably incorrect example:

  expect(output).to match /\w+ \w+ +\d+ \d+:\d+:\d+ \w+ \d+/

Or use, e.g., the timecop gem so that the test can force the time, and then check for an exact match on the date.

This server has no side-effects, but if it did, I would put the side-effects in their own objects. Let's say that the server can reboot the box when the user's name is "Barney". Instead of having the server do that directly, let it call a "rebooter" object. In your test, create a fake rebooter and give that to the server:

before do
  @rebooter = double(Rebooter)
  @server = Server.new(rebooter: @rebooter)
  @server.start
end

and then:

"it should reboot when Barney connects" do
  @rebooter.should_receive(:reboot)
  @socket = TCPSocket.new("localhost", @server.port)
  @socket.puts "Barney"
  @socket.close_write
  output = @socket.read
end
Wayne Conrad
  • 103,207
  • 26
  • 155
  • 191
  • Thanks Wayne! I have a couple of questions about this. You say that a good way to do this would be to set up and tear down the server in the test set-up as in your example. When I run rspec, the tests don't actually run. They get stuck half way through and I have to Ctrl + C out. What is happening here? Why do the tests not run? Is it something to do with the way I'm making a connection to the server? It doesn't even reach the after block to close it down, it just hangs. – ssks Dec 11 '14 at 12:39
  • @ssks I'm not sure why your code hangs. The problem is that I can't make sense of your code: The code under test runs at load time (it's a simple script), which is not normally how you'd set up a server to test it. I don't understand why the code under test is a server, _and_ the test is also creating a server. There should be a TCPSocket somewhere in the test to create a client connection to the server. – Wayne Conrad Dec 11 '14 at 13:53