Question

I have a Sinatra app that serves pages as read-only or editable depending on if the user is logged in.

The controller sets a variable @can_edit, that is used by the views to hide/show edit links. How can I test @can_edit's value in my tests? I have no idea how to get at the current instance of the controller under Rack::Test.

I use class_eval to stub the logged_in? method in the controller, but I'm having to resort to checking last_response.body for my edit links to see if @can_edit has been set or not.

How can I test the value of @can_edit directly?

Was it helpful?

Solution

Unfortunately I don't think this is possible without modifying Rack::Test. When you make a request during application testing, Rack::Test does the following:

  1. adds the request to a list of recent requests
  2. creates a new instance of your application and invokes its call method
  3. adds your application's response to a list of recent responses

It's easy to access the last_request and last_response, but unfortunately no information is saved about the state of your application while it's running.

If you're interested in hacking together a Rack::Test patch to do this, start by looking at rack-test/lib/rack/mock_session.rb on line 30. This is where Rack::Test runs your application and receives the standard Rack app return values (status, headers, body). My guess is that you're going to have to modify your application as well, to collect and make accessible all of its instance variables.

In any case, it's best to test for results, not implementation details. If you want to make sure an edit link is not visible, test for the presence of the edit link by DOM id:

assert last_response.body.match(/<a href="..." id="...">/)

OTHER TIPS

It is possible with a little hack. Sinatra app's instances are unavailable because they are created when Sinatra::Base#call is called. as Alex explained. This hack prepares an instance on ahead and let the next call grab it.

require 'something/to/be/required'

class Sinatra::Base
  @@prepared = nil

  def self.onion_core
    onion = prototype
    loop do
      onion = onion.instance_variable_get('@app')
      return onion if onion.class == self || onion.nil?
    end
  end

  def self.prepare_instance
    @@prepared = onion_core
  end

  # Override
  def call(env)
    d = @@prepared || dup
    @@prepared = nil
    d.call!(env)
  end
end

describe 'An Sinatra app' do
  include Rack::Test::Methods

  def app
    Sinatra::Application
  end

  it 'prepares an app instance on ahead' do
    app_instance = app.prepare_instance    
    get '/foo'
    app_instance.instance_variable_get('@can_edit').should be_true
  end
end

I figured out this technique to mock the instance that runs the current test in the first place.

Heres a nasty but viable alternative

# app.rb - sets an instance variable for all routes
before do
  @foo = 'bar'
end

# spec.rb
it 'sets an instance variable via before filter' do
  my_app = MySinatraApplication
  expected_value = nil
  # define a fake route
  my_app.get '/before-filter-test' do
    # as previously stated, Sinatra app instance isn't avaiable until #call is performed
    expected_value = @foo
  end
  my_app.new.call({
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => '/before-filter-test',
    'rack.input' => StringIO.new
  })
  expect(expected_value).to eq('bar')
end

This allows you to test against a sinatra before filter and or access instance variables created for base application.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top