Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a Sinatra app instance method from TestCase

Tags:

ruby

sinatra

rack

I have an util method into a Sinatra application and I would like to tested from my TestCase.

The problem is that I don't know how to invoke it, if I just use app.util_method I have the error NameError: undefined local variable or method 'util_method' for #<Sinatra::ExtendedRack:0x007fc0c43305b8>

my_app.rb:

class MyApp < Sinatra::Base
  # [...] routes methods

  # utils methods
  def util_method
    return "hi"
  end
end

my_app_test.rb:

require "my_app.rb"
require "test/unit"
require "rack/test"

class MyAppTest < Test::Unit::TestCase
  include Rack::Test::Methods

  def app
    MyApp.new
  end

  # [...] routes methods tests

  def test_util_method
    assert_equal( "hi", app.util_method )
  end
end
like image 989
fguillen Avatar asked Aug 22 '12 12:08

fguillen


2 Answers

Sinatra aliases the new method to new! before redefining it, so the simplest solution is to use that instead:

def app
  MyApp.new!
end

Of course I only noticed that after I’d come up with the following, which I’ll leave in as it could be useful/informative.


A possible way to get round Sinatra redefining the new method and returning a complete Rack app a get hold of an instance your actual base class is to do what the “real” new method does yourself:

def app
  a = MyApp.allocate
  a.send :initialize
  a
end

This is a bit of a hack, but it might be useful for testing.

Another technique would be to “walk” the middleware stack until you got to your class. The following is a little fragile, as it depends on all the middleware involved to use the name @app to refer to the next app in the stack, but this is fairly common.

def app
  a = MyApp.new
  while a.class != MyApp
    a = a.instance_variable_get(:@app)
  end
  a
end

That won’t work on the yet to be released Sinatra 1.4 though (at least not on the current master, which is commit 41840746e866e8e8e9a0eaafc53d8b9fe6615b12), as new now returns a Wrapper class and the loop never ends. In this case you can grab the base class directly from the @instance variable:

def app
  MyApp.new.instance_variable_get :@instance
end

(note this last technique may well change before the final 1.4 release).

like image 173
matt Avatar answered Nov 18 '22 03:11

matt


The problem you are encountering is, that MyApp.new does not return an instance of MyApp but an instance of the middleware wrapping your App (usually Rack::Head or Sinatra::ShowExceptions). A good explanation can be found in the thread Sinatra Usage Question / Rack App.

The only solution I can think of is to change your instance method to a class method which can be called without the instance itself. As the instance of your App may be freshly instantiated for every request, an instance method probably doesn't have much advantages over a class method in your scenario.

Edit:

In the upcoming Sinatra 1.4 the initialization will change. Sinatra::Base.new will return a Sinatra::Wrapper instance, which exposes #settings and #helpers. This may help solve the problem of accessing Sinatra::Base instance methods. See the Sinatra Changelog for more information.

like image 30
Florian Feldhaus Avatar answered Nov 18 '22 02:11

Florian Feldhaus