Using Cucumber with Modular Sinatra Apps

Using Cucumber With Modular Sinatra Apps

Update to include dealing with multiple Sinatra apps

Require the file where your app comes together and change

  def app
Sinatra::Application
end

to

 def app
Rack::Builder.new do
map '/a' { run MyAppA }
map '/b' { run MyAppB }
end
end

and just test the app proper.

eg, if you define middleware in your config.ru that you want to test, maybe move loading those into your app's definition.

Setup Cucumber, Capybara and RSpec with Modular Sinatra application

set your env.rb:

ENV['RACK_ENV'] = 'test'
require File.join(File.dirname(__FILE__), '..', '..', 'config/boot.rb')

require 'capybara'
require 'capybara/cucumber'
require 'rspec'

Capybara.app = eval("Rack::Builder.new {( " + File.read(File.dirname(__FILE__) + '/../../config.ru') + "\n )}")

class SomeWorld
include Capybara
include RSpec::Expectations
include RSpec::Matchers
end

World do
SomeWorld.new
end

And It'll work!

Set up cucumber to test sinatra app using watir

env.rb executes an arbitrary Ruby code as a part of Cucumber setup, so you should be able to start your app or do whatever else you need. Try:

require 'YourApp'
YourApp.run!

Having said that, it might not be the most logical place to do. An automated build cycle for a web app usually consists of the following steps:

  1. Build: compile, copy necessary files, package a binary, etc.
  2. Run unit tests
  3. Deploy: put the application on a target server
  4. Run integration/feature tests

Cucumber and Watir are all about Step 4; therefore, env.rb usually starts a browser. What you are trying to do belongs logically in Step 3, IMHO.

Error handlers don't run in modular Sinatra app

The use method is for adding middleware to an app, you can’t use it to compose an app like this.

In your example you actually have three different Sinatra applications, two of which are being run as middleware. When a Sinatra app is run as middleware then any request that matches one of its routes is handled by that app, otherwise the request is passed to the next component in the Rack stack. Error handlers will only apply if the request has been handled by the same app. The app that you have defined the error handlers in has no routes defined, so all requests will be passed on down the stack — the error handlers will never be used.

One way to organise a large app like this would be to simply use the same class and reopen it in the different files. This other question has an example that might be useful: Using Sinatra for larger projects via multiple files.

Architecture for a modular, component-based Sinatra Application

This is similar to include's proposal, but it doesn't require access to the rackup file.

Write your various Handlers like:

class FoodHandler < Sinatra::Base
get '/chunky/:food' do
"Chunky #{params[:food]}!"
end
end

Then in your main application file:

require './lib/handlers/food_handler.rb'

class Main < Sinatra::Base
enable :sessions
... bla bla bla
use FoodHandler
end

I've used this kind of structure to build some fairly complex Sinatra apps. It scales just as well as Rails.

EDIT

To have your config file define the routes, you could do something like this:

class PlacesHandler < Sinatra::Base
# Given your example, this would define 'places/paris' and 'places/losangeles'
CONFIG['components'].select { |c| c['compontent_type'] == 'Mapper' }.each do |c|
get c['route'] do
@latitude = c['component_settings']['latitude']
@longitude = c['component_settings']['longitude']
end
end
end

Converting to modular sinatra app breaks tests

I think you just need to change your app method to return your modularised application class (MyApp) rather than the Sinatra::Base class. So replace:

def app() Sinatra::Base end

in web_spec.rb, with:

def app
MyApp
end

Rack::Test::Methods depends on the app method to tell it which class to call to process requests. In a simple non-modular Sinatra application that class is Sinatra::Base because that is the class in which the routes are applied by default. In a modular application that is the class in which you define your routes (MyApp in your case).

Access Sinatra settings from a model

I ended up doing:

#document.rb
class Document
def self.documents_path=(path)
@documents_path = path
end
def self.documents_path
@documents_path
end
...
end

#app.rb
configure do
set :documents_path, settings.root + "/../documents/"
Document.documents_path = settings.documents_path
end

then just using Document.documents_path inside my find method.

Set Up for RSpec in a Sinatra modular app

I'm guessing you're following this recipe, correct?

The described_class in this line:

def app() described_class end

is meant to be the class under test, in this case ZerifApp. Try it like so:

def app() ZerifApp end

EDIT

It turns out the above answer is not correct about what described_class does. I assumed it was a placeholder -- actually it is an RSpec method that returns the class of the implicit subject, that is to say, the thing being tested.

The recipe at the link is misleading because of the way it recommends writing the describe block:

describe "My Sinatra Application" do

This is valid RSpec, but it does not define the subject class. Executing described_class in an example for this block will return nil. To make it work, replace the describe block:

describe ZerifApp do

Now described_class will return the expected value (ZerifApp)

How do I config.ru properly in modular Sinatra application.?

With URLMap you specify a base url where the app should be mounted. The path specified in the map is not used when determining which route to use within the app itself. In other words the app acts as if it's root is after the path used in URLMap.

For example, your code will respond to the following paths:

  • /: will be routed to the / route in MyApp

  • /another: will go to the / route in AnotherRoute. Since AnotherRoute extends MyApp this will be the same as / in MyApp (but in a different instance).

    URLMap sees /another and uses it to map to AnotherRoute, stripping this part of the request from the path. AnotherRoute then only sees /.

  • /another/another: will be routed to the two /another routes in AnotherRoute. Again, the first another is used by the URLMap to route the request to AnotherRoute. AnotherRoute then only sees the second another as the path.

    Note that this path will respond to both GET and POST requests, each being handled by the appropriate block.

It's not clear what you're trying to do, but I think you can achieve what you want by running an instance of AnotherRoute, with a config.ru that is just:

run AnotherRoute.new

Since AnotherRoute extends MyApp, the / route will be defined for it.

If you're looking for a way to add routes to an existing Sinatra application, you could create a module with an included method that adds the routes, rather than use inheritance.



Related Topics



Leave a reply



Submit