Silencing Deprecation Warnings in Rspec
If you’re testing the behavior of deprecated code in your Ruby project, the warning messages littered throughout your spec output is incredibly noisy.
You could silence all warnings with ::ActiveSupport::Deprecation.silenced = true, but you might miss out on an important warning in one of your dependencies. It’s tempting to remove the tests altogether (the code will be burned soon too, right?), but I figured out something a little nicer a little while back in Formtastic’s test suite.
How to: Use git bisect to find bugs and regressions
Git allows you to do a binary search across commits to hunt down the commit that introduced a bug.
Given you are currently on your branch's HEAD that is not working as expected, an example workflow could be:
git bisect start # Start bisecting
git bisect bad # Tag the revision you are currently on (HEAD) as bad. You could also pass a commit's SHA1 like below:
git bisect good abcdef12345678 # Give the SHA1 of any commit that was working as it should
# shorthand:
git bisect start <bad ref> <good ref>
Git will fetch a comm...
Test a download's filename with Cucumber
These steps are now part of Spreewald.
The step definitions below allow you to test the filename suggested by the server:
When I follow "Export as ZIP"
Then I should get a download with the filename "contacts_20110203.zip"
Capybara
Then /^I should get a download with the filename "([^\"]*)"$/ do |filename|
page.driver.response.headers['Content-Disposition'].should include("filename=\"#{filename}\"")
end
Webrat
Then /...
Test the content-type of a response in Cucumber
The step definitions below allow you to write this in both Webrat and Capybara:
When I follow "Download as PDF"
Then I should get a response with content-type "application/pdf"
Capybara
Then /^I should get a response with content-type "([^"]*)"$/ do |content_type|
page.response_headers['Content-Type'].should == content_type
end
Webrat
Then /^I should get a response with content-type "([^"]*)"$/ do |content_type|
response.content_type.should == content_type
end
Unfortunatly this do...
Cucumber steps to test input fields for equality (with wildcard support)
Our collection of the most useful Cucumber steps, Spreewald, now supports exact matching of form fields and lets you use wildcards.
Examples:
And the "Money" field should contain "134"
# -> Only is green if that field contains the exact string "134", neither "134,50" nor "1000134"
And the "Name" field should contain "*Peter*"
# -> Accepts if the field contains "Peter" or "Anton Peter" or "Peter Schödl" etc.
And the "Comment" field should contain "Dear*bye"
# -> Accepts if the field contains "De...
Apache: Redirect all requests from one host to another
In order to redirect all requests from redirecting-host.com
to desired-host.com
while keeping path and query params unchanged, change your Apache VHost to something like this:
ServerName desired-host.com
ServerAlias redirecting-host.com
RewriteEngine On
RewriteCond %{HTTP_HOST} !^desired-host.com$
RewriteRule ^.*$ http://desired-host.com%{REQUEST_URI} [R=301,L]
Take care to keep all those ^
, $
and !
as seen in the example.
Parametrized shared example groups in RSpec
RSpec 2
In RSpec 2 shared_examples_for
can have parameters. You can simply hand over arguments from it_behaves_like
:
shared_examples_for 'string equaling another string' do |expected_string|
it 'is equal to another string' do
expect(subject).to eq(expected_string)
end
end
describe 'some string' do
subject { 'foo' }
it_behaves_like 'string equaling...
Inspect the page content in a Cucumber session
When you need to see the content of a page (i.e. not all the HTML but the relevant text body)
- you can do
pp (html_content)
- pp will format the html String human readable pretty printed
- where html_content can be replaced by one of the following commands:
Rails
body
or response.body
Capybara:
page.driver.html.content
page.body
Webrat:
Nokogiri::HTML(response.body).content
The returned strings can be cleaned up by calling .gsub(/^\s*$/, '').squeeze("\n")
on them.\
Although this may be useful for d...
RSpec matcher to check if two numbers are the same
You can usually just use the eq
matched to compare two numbers:
expect(deal.total).to eq(120)
If the actual value is a BigDecimal
, you might have issues when you match it against a Float
:
expect(deal.total_price).to eq(1200.99)
In these cases, try matching it against another BigDecimal
:
expect(deal.total_price).to eq BigDecimal(1200.99)
If you don't like the syntax, our rspec_candy gem has a matcher that will compare Fixnums
(integers), Floats
and `BigDecima...
Test that an exception or error page is raised in Capybara
You can use these step definitions:
Then /^I should not see an error$/ do
(200 .. 399).should include(page.status_code)
end
Then /^I should see an error$/ do
(400 .. 599).should include(page.status_code)
end
Note that you need to tag the scenario with @allow-rescue
to test that an error is shown like this
@allow-rescue
Scenario: Accessing the admin area requires a login
When I go to the admin area
Then I should see an error
These step definitions will not work for @javascript
scena...
Preloaded associations are filtered by conditions on the same table
When you eagerly load an association list using the .include
option, and at the same time have a .where
on an included table, two things happen:
- Rails tries to load all involved records in a huge single query spanning multiple database tables.
- The preloaded association list is filtered by the
where
condition, even though you only wanted to use thewhere
condition to filter the containing model.
The second case's behavior is mostly unexpected, because pre-loaded associations usually don't care about the circumstances under whi...
Check if two arrays contain the same elements in Ruby, RSpec or Test::Unit
RSpec 1, RSpec 2
To test whether two arrays have the same elements regardless of order, RSpec 1 and 2 give you the =~
matcher:
actual_array.should =~ expected_array
Rspec 3
With RSpec 3's expect
syntax you can choose one of these two matchers:
expect(actual_array).to match_array(['1', '2', '3'])
expect(actual_array).to contain_exactly('1', '2', '3')
Note how match_array
takes an argument, but contain_exactly
takes a list of elements as varargs.
Test::Unit
If y...
Hints for debugging MySQL InnoDB deadlocks
Deadlocks only occur if two transactions in separate threads compete for the same rows in the database. They usually (but not necessarily) only happen when trying to update or otherwise lock several rows in different order.
Solving deadlocks is potentially complicated, so here are a few pointers:
- MySQL should always detect the deadlock right when it happens, and will throw an error to one of the offending threads. This error states the SQL statement that this thread was currently waiting for, and that tried to acquire one of the competin...
How to fix failing controller specs 91% of the time
If your controller spec never reaches your controller code:
-
Make sure you are signed in.
-
Make sure you are actually triggering a request by saying
get :edit
or something siliar. -
Know that views are not rendered by default for controller specs unless you tell them to (
render_views
).
^
describe UsersController do
describe '#edit' do
it 'should work' do
sign_in
get :edit
end
end
enddefine something like this in your spec_helper.rb:
def sign_in(user = User....
Virtual attributes for array fields
When a has_many
association basically serves to store a list of associated strings (tags, categories, ...), it can be convenient to represent this association as a string array in the containing model. Here is an example for this pattern from the acts-as-taggable-on gem:
post = Post.last
p post.tag_list # ['foo', 'bar', 'baz']
post.tag_list = ['bam']
p post.tag_list # ['bam']
This string array tag_list
is magical in several ways:
- It is read from and written to a `has...
Hunt down that elusive debug message in Ruby
When you just went through a long debug-fest and infested your code with dozens of debug messages, it can be hard to find all those calls to puts
and p
. This note describes a hack that lets you trace those messages in your code.
Let's say you want to get rid of a console message "foobar". Copy the Undebug
class below to config/initializers.rb
. In the same initializer, type a line:
Undebug.trace_message('foobar')
Now run tests or whatever you need to do to to trigger that message. The console output should look like this:
...
Don't use migrations to seed default data
Don't insert table rows in a Rails database migration. This will break tests that expect that database to be empty and cause you all sorts of pain.
If you need a place for default application data, use db/seed.rb or put a script into lib/scripts
. It won't run automatically, so add a chore story to Pivotal Tracker as a reminder.
Take care when joining and selecting on scopes
Occasionally some complex query must be processed on the database because building thousands of Ruby objects is impracticable.
Many times you would use scope options, like this:
users = User.scoped(
:joins => 'INNER JOIN orders joined_orders ON users.id = joined_orders.user_id',
:conditions => [ 'joined_orders.date BETWEEN ? AND ?', start_date, end_date ],
:select => '*, SUM(joined_orders.amount) AS amount_sum',
:group => 'users.id'
)
You get ActiveRecord objects and you can ask each of them about its `amou...
Use the back button in Cucumber
In order to go back one page in your Cucumber tests, you can use the following step definition for Capybara:
When(/^I go back$/) do
visit page.driver.request.env['HTTP_REFERER']
end
If you're on Webrat, this should work:
When(/^I go back$/) do
visit request.env["HTTP_REFERER"])
end
An improved version of this step is now part of our gem spreewald on Github.
Test a gem in multiple versions of Rails
Plugins (and gems) are typically tested using a complete sample rails application that lives in the spec
folder of the plugin. If your gem is supposed to work with multiple versions of Rails, you might want to use to separate apps - one for each rails version.
For best practice examples that give you full coverage with minimal repitition of code, check out our gems has_defaults and assignable_values. In particular, take a look at:
- Multiple `sp...
Test that a select option is selected with Cucumber
This step tests whether a given select option comes preselected in the HTML. There is another step to test that an option is available at all.
Capybara
Then /^"([^"]*)" should be selected for "([^"]*)"(?: within "([^\"]*)")?$/ do |value, field, selector|
with_scope(selector) do
field_labeled(field).find(:xpath, ".//option[@selected = 'selected'][text() = '#{value}']").should be_present
end
end
Webrat
...
Bookmarklet to generate a commit message with Pivotal Tracker story ID and title
For clarity and traceability, your commit messages should include the ID and title of the Pivotal Tracker story you're working on. For example:
[#12345] Add Google Maps to user profiles
Optional further commit messages in the body
Also see Howto: Write a proper git commit message
To quickly generate such commit messages, add a new link "Commit" to your bookmarks and use the following Javascript as the link URL:
javascript:(function() { ...
Aliases for routes
The following initializer provides an :alias => "my_route_name"
option to restful routes in your route.rb
. This simply makes the same route also available under a different ..._path / ..._url helpers.
For example,
map.resources :notes, :alias => :snippets
Gives you
notes_path, notes_url, new_note_path... #as always
snippets_path, snippets_url, new_snippet_path... #from the alias
Put this into an initializer:
Test that a CSS selector is present with Cucumber
This note describes a Cucumber step definition that lets you test whether or not a CSS selector is present on the site:
Then I should see an element "#sign_in"
But I should not see an element "#sign_out"
Here is the step definition for Capybara:
Then /^I should (not )?see an element "([^"]*)"$/ do |negate, selector|
expectation = negate ? :should_not : :should
page.send(expectation, have_css(selector))
end
Here is the step definition for Webrat:
Then /^I should (not )?see an element "([^"]*)"$/ do |negate...