Configuring rack-test to start the server indirectly

462 Views Asked by At

Here is my rack application:

class MainAppLogic
    def initialize
        Rack::Server.start(:app =>Server, :server => "WEBrick", :Port => "8080")
    end
end

class Server
    def self.call(env)
        return [200, {},["Hello, World"]]
    end
end

When actually run, it behaves as it should and returns "Hello World" to all requests. I'm having trouble convincing rack-test to work with it. Here are my tests:

require "rspec"
require "rack/test"
require "app"

# Rspec config source: https://github.com/shiroyasha/sinatra_rspec    
RSpec.configure do |config|
    config.include Rack::Test::Methods
end

describe MainAppLogic do

    # App method source: https://github.com/shiroyasha/sinatra_rspec
    def app
        MainAppLogic.new
    end

    it "starts a server when initialized" do
        get "/", {}, "SERVER_PORT" => "8080"
        last_response.body.should be != nil
    end
end

When I test this, it fails complaining that MainAppLogic is not a rack server, specifically, that it doesn't respond to MainAppLogic.call. How can I let it know to ignore that MainAppLogic isn't a rack server and just place a request to localhost:8080, because there server has started?

2

There are 2 best solutions below

2
On BEST ANSWER

First thing: why the custom class to run the app? You can use the rackup tool, which is the de-facto standard for running Rack apps. Some more details on it here.

Your app code then becomes:

class App
  def call(env)
    return [200, {}, ['Hello, World!']]
  end
end

and with the config.ru

require_relative 'app'

run App.new

you can start the app by running rackup in your project's directory.

As for the error, the message is pretty clear. rack-test expects, that the return value of app method would be an instance of a rack app (an object that responds to call method). Take a look what happens in rack-test internals (it's pretty easy to follow, as a tip—focus on these in given order: lib/rack/test/methods.rb#L30 lib/rack/mock_session.rb#L7 lib/rack/test.rb#L244 lib/rack/mock_session.rb#L30. Notice how the Rack::MockSession is instantiated, how it is used when processing requests (e.g. when you call get method in your tests) and finally how the call method on your app is executed.

I hope that now it's clear why the test should look more like this (yes, you don't need to have a server running when executing your tests):

describe App do
  def app
    App.new
  end

  it "does a triple backflip" do
    get "/"
    expect(last_response.body).to eq("Hello, World")
  end
end

P.S. Sorry for the form of links to rack-test, can't add more than 2 with my current points :P

3
On

Your app should be the class name, for example instead of:

def app
  MainAppLogic.new
end

You have to use

def app
  MainAppLogic
end

You shouldn't need to indicate the port for doing the get, because the rack app runs in the context of the tests; so this should be right way:

it "starts a server when initialized" do
    get "/"
    last_response.body.should be != nil
end

Also, as a recommendation prefer to use the new expect format instead of the should, see http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/

And your MainAppLogic, should be something like:

class MainAppLogic < Sinatra::Base
  get '/' do
    'Hello world'
  end
end