Features
Testing
Padrino apps are Rack apps, so you test them with rack-test: send a request, then check the response. Choose a test framework, and optionally a mocking library, when you generate the project:
$ padrino g project demo -t rspec
$ padrino g project demo -t minitest -m mocha
The project generator lists every test and mock
component. The generated project has a test helper and a rake task: RSpec uses
spec/spec_helper.rb; minitest, test-unit, shoulda and bacon use
test/test_config.rb; cucumber uses features/support/env.rb. The controller,
model and helper generators add a test file for what they create.
Run the suite, or a single file:
$ bundle exec rake spec # RSpec
$ bundle exec rake test # minitest, test-unit, shoulda and bacon
$ bundle exec rspec spec/app/controllers/posts_controller_spec.rb
$ bundle exec ruby -Itest test/app/controllers/posts_controller_test.rb
Requests
The test helper includes Rack::Test::Methods, so a test can call get,
post, put, delete and the other verbs, then read last_response:
# spec/app/controllers/posts_controller_spec.rb
require 'spec_helper'
RSpec.describe '/posts' do
it 'lists the posts' do
get '/posts'
expect(last_response).to be_ok
expect(last_response.body).to eq 'Hello World'
end
it 'creates a post' do
post '/posts/create', title: 'Hi'
expect(last_response.body).to eq 'created Hi'
end
end
The same test with minitest:
# test/app/controllers/posts_controller_test.rb
require File.expand_path("#{__dir__}/../../test_config.rb")
describe '/posts' do
it 'lists the posts' do
get '/posts'
assert last_response.ok?
assert_equal 'Hello World', last_response.body
end
end
Requests go to the app method defined in the test helper. By default it
returns Padrino.application, which serves every mounted app at its mount
point, so a request to an app mounted at /blog starts with /blog. To test
one app on its own, return that app class instead (shown here with minitest).
Request paths then don't include the mount point:
describe 'the blog' do
def app
Demo::Blog
end
it 'lists the posts' do
get '/posts'
assert last_response.ok?
end
end
URLs the app generates (url, redirect, form actions) still include the mount
point, so a test that follows a redirect needs Padrino.application.
The test environment
The test helper defines RACK_ENV = 'test' before it loads config/boot.rb. The
generated config/apps.rb turns CSRF protection off in that environment:
set :protect_from_csrf, Padrino.env != :test
So a test can post without an authenticity_token. To test the protection
itself, see CSRF Protection.
Sessions
rack-test keeps cookies between the requests of one test, so a session started
by one request (a login, for example) is there for the next. To start a test
with values already in the session, pass them in the rack.session entry of
the request's Rack env, the third argument:
it 'shows the posts of the signed-in user' do
get '/posts/mine', {}, 'rack.session' => { user_id: 1 }
expect(last_response.body).to eq 'posts of user 1'
end
To check what a request stored in the session, read it from last_request:
post '/posts/create', title: 'Hi'
expect(last_request.env['rack.session'][:last_title]).to eq 'Hi'
Stubbing helpers
Helpers are instance methods of the app, and each request runs on a new
instance of the app class. To stub a helper such as current_user in a
controller test, stub it on any instance of the app class that handles the
request.
With RSpec's own mocks (a project generated without -m):
it 'shows the posts of the current user' do
allow_any_instance_of(Demo::App).to receive(:current_user).and_return('alice')
get '/posts/mine'
expect(last_response.body).to eq 'posts of alice'
end
With mocha (-m mocha, with any test framework, including RSpec):
it 'shows the posts of the current user' do
Demo::App.any_instance.stubs(:current_user).returns('alice')
get '/posts/mine'
assert_equal 'posts of alice', last_response.body
end
Both remove the stub when the test ends. The generator sets mocha up for RSpec, minitest, test-unit and shoulda; with bacon it only adds the gem and prints how to set it up.