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...
Bash Cheat Sheet (standard Emacs mode)
-
Ctrl + R Search commands you entered previously. Press Ctrl + R again to search further back, Ctrl + Shift + R searches forward again.
-
Ctrl + W Deletes from the cursor position to the left.
-
Ctrl + _ Undo. Yes, this also works with a German keyboard layout.
-
Ctrl + L Clear screen.
-
Ctrl + D _Close shell. (EOT, just like in many other shells.) Note: if you dove into another shell (e.g. with
sudo su username
) you will close it and return to ...
Standalone Cucumber Test Suite
Sometimes you inherit a non Rails or non Rack based web app such as PHP, Perl, Java / JEE, etc. I like using cucumber for functional testing so I put together this project structure to use as a starting point for testing non Ruby web based applications.
Request a non-HTML format in controller specs
If a controller action responds to other formats than HTML (XML, PDF, Excel, JSON, ...), you can reach that code in a controller spec like this:
describe UsersController do
describe '#index' do
it 'should be able to send an excel file' do
# stubs and expectations go here
get :index, :format => 'xls'
end
end
end
Remember that both the :format
parameter and the HTTP_ACCEPT
header can m...
Migrating to RSpec 2 from RSpec 1
You will need to upgrade to RSpec >= 2 and rspec-rails >= 2 for Rails 3. Here are some hints to get started:
- In RSpec 2 the executable is
rspec
, notspec
. - RSpec and rspec-rails have been completely refactored internally. All RSpec classes have been renamed from
Spec::Something
toRSpec::Something
. This also means that everyrequire 'spec/something'
must now berequire 'rspec/something'
. - In
spec_helper.rb
,Spec::Runner.configure
becomesRSpec.configure
- It has become really hard to extend specific example groups ...
Getting your e-mails back after upgrading Thunderbird to version 3
If you previously used version 2.x of Thunderbird and upgraded to 3.x (for example through an Ubuntu release upgrade) you might notice that Thunderbird will not show any of your old e-mails or settings.
This results from a different directory being used for storing profiles and configuration.
You can replace the blank profile with your old one like this:
cd ~
mv .thunderbird .thunderbird-invalid
cp -R .mozilla-thunderbird .thunderbird
Upon its next start, Thunderbird brings up the migration wizard introducing you to a few vers...
Force RubyMine to notice file system changes
If you did file operations inside a shell or for example using Nautilus, it can take quite a while until RubyMine takes note of them and updates things like your project tree or its internal file list.
Flushing file system buffers helps you out (run it from a terminal):
sync
This is also possibly via the RubyMine menus: File → Synchronize.
Setting nil values in Machinist blueprints
Take care when trying to set attributes to nil
in a blueprint.
Given the following master blueprint:
Story.blueprint do
title
author { User.make }
editor { User.make }
end
This approach will not overwrite/remove the editor
defined in the master blueprint:
Story.blueprint(:draft) do
editor nil
end
...whereas this one will (note the lambda):
Story.blueprint(:draft) do
editor { nil }
end
Match strings in a given order with Cucumber and Capybara
Sometimes the order in which strings appear on a page matters to you.
Spreewald gives you steps like these:
Then I should see in this order:
| Alpha Group |
| Augsburg |
| Berlin |
| Beta Group |
Or, if you prefer multiline strings:
Then I should see in this order:
"""
Alpha Group
Augsburg
Berlin
Beta Group
"""
The step ignores all HTML tags and only tests on plain text.