Search code examples
rubyrspecrack-test

Configuring rack-test to start the server indirectly


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?


Solution

  • 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