Things to consider when using Travis CI
Travis CI is a free continuous integration testing service. However, it is really fragile and will break more than it will work.
If you choose to use it anyway, learn the lessons we already learnt:
Use a compatible Rubygems for Rails 2.3 on Ruby 1.8.7
Ruby 1.8.7 is not compatible with current Rubygems versions (> 2.0). Runnig rvm rubygems latest-1.8 --force
will fix this and install Rubygems version 1.8.29.
To make Travis CI do this, add `before_script: rvm rubygems latest-1....
Deal with certain travis CI failures
Travis changed their default distribution from Ubuntu 14.04 (trusty) to 16.04 (precise). This might break your test setup for new builds.
You can solve this issue by freezing your test distribution in the .travis.yml
to Ubuntu 14.04 until you have the time to solve all the issues you will have in 16.04:
dist: trusty
Error details
Here are few indicators that you ran into this issue:
Connection to the PostgreSQL database does not work anymore
Your travis-ci builds might have started failing on the usual
psql -c...
howto fix spreewald issue „database configuration does not specify adapter (ActiveRecord::AdapterNotSpecified)“
This error occurs when you already have a database.yml
which defines the database for the cucumber
environment instead of test
. (Spreewald database.sample.yml
has changed)
Fix
Change cucumber
to test
in your databse.yml
test: # <---
adapter: mysql2
database: spreewald_test
encoding: utf8
host: localhost
username: root
password: password
How to change the hostname in Cucumber features
Capybara uses www.example.com
as the default hostname when making requests.
If your application does something specific on certain hostnames and you want to test this in a feature, you need to tell Capybara to assume a different host.
Given /^our host is "([^\"]+)"$/ do |host|
page.config.stub app_host: "http://#{host}"
# In older Capybaras (< 2.15) you needed to do this instead:
Capybara.stub app_host: "http://#{host}"
end
You can now say:
When I go to the start page
Then I should not see "Home ...
Ruby: Writing specs for (partially) memoized code
When you're writing specs for ActiveRecord models that use memoization, a simple #reload
will not do:
it 'updates on changes' do
subject.seat_counts = [5]
subject.seat_total.should == 5
# seat_total is either memoized itself, or using some
# private memoized method
subject.seat_counts = [5, 1]
subject.seat_total.reload.should == 6 # => Still 5
end
You might be tempted to manually unmemoize any memoized internal method to get #seat_total
to update, but that has two disadvant...
Savon testing: How to expect any message
When using Savon to connect a SOAP API, you may want to use Savon::SpecHelper
to mock requests in your tests as described in their documentation.
When sending a message body, the savon
mock object requires a message to be set, like this:
savon.expects(:action_name).with(message: { user_id: 123 }).returns('<some xml>')
If you want to stub only the returned XML and do not care about request arguments, you can not omit with
as Savon's helper will complain:
savo...
rspec_candy 0.2.0 now comes with our most popular matchers
Our rspec_candy gem now gives you three matchers:
be_same_number_as
Tests if the given number is the "same" as the receiving number, regardless of whether you're comparing Fixnums
(integers), Floats
and BigDecimals
:
100.should be_same_number_as(100.0)
50.4.should be_same_number_as(BigDecimal('50.4'))
Note that "same" means "same for your purposes". Internally the matcher compares normalized results of #to_s
.
be_same_second_as
...
How to create Excel sheets with spreadsheet gem and use number formats for cells like money or date
The following snippet demonstrates how you could create excel files (with spreadsheet gem) and format columns so that they follow a specific number format like currencies or dates do.
require 'rubygems'
require 'spreadsheet'
Spreadsheet.client_encoding = 'UTF-8'
book = Spreadsheet::Workbook.new
sheet1 = book.create_worksheet :name => 'test'
money_format = Spreadsheet::Format.new :number_format => "#,##0.00 [$€-407]"
date_format = Spreadsheet::Format.new :num...
SearchableTrait is now a gem: Dusen
For two years we've been using SearchableTrait
which gives models the ability to process Googlesque queries like this:
Contact.search('a mix of words "and phrases" and qualified:fields')
This trait used to be a huge blob of code without tests and documentation, so I made a gem out of it. Check out https://github.com/makandra/dusen for code, tests, and a huge README.
You should use the Dusen gem and delete SearchableTrait
in all future projects.
Note that the syntax to define query proc...
Working around OpenSSL::SSL::SSLErrors
If your requests blow up in Ruby or CURL, the server you're connecting to might only support requests with older SSL/TLS versions.
You might get an error like: OpenSSL::SSL::SSLError: SSL_connect SYSCALL returned=5 errno=0 state=unknown state
SSL Server Test
This SSL Server Test can help finding out which SSL/TLS versions the server can handle.
Ruby
In Ruby, you can teach Net::HTTP
to use a specific SSL/TLS version.
uri = URI.parse(url)
ssl_options = {
use_ssl: true,
ssl_version...
SSHKit 1.9.0 failure for Capistrano deploy
SSHKit 1.9.0 might fail with the following error, when trying to deploy a Rail application. Upgrading the gem to version 1.21.0 fixed the issue.
Traceback (most recent call last):
17: from /home/user/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/sshkit-1.9.0/lib/sshkit/runners/parallel.rb:12:in `block (2 levels) in execute'
16: from /home/user/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/sshkit-1.9.0/lib/sshkit/backends/abstract.rb:29:in `run'
15: from /home/user/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/sshkit-1.9....
Structuring Rails applications: the Modular Monorepo Monolith
Root Insurance runs their application as a monolithic Rails application – but they've modularized it inside its repository. Here is their approach in summary:
Strategy
- Keep all code in a single repository (monorepo)
- Have a Rails Engine for each logical component instead of writing a single big Rails Application
- Build database-independent components as gems
- Thus: gems/ and engines/ directories instead of app/
- Define a dependency graph of components. It should have few edges.
- Gems and Engines can be extracted easier once nece...
Capistrano 2: How to deploy a single server
When you have a multi-server setup, you'll be adding a new server from time to time. Before doing a full deploy, you might want to test that server in an isolated deploy. There is a single way to do this: the HOSTFILTER
env variable.
Commenting out "server" lines in the Capistrano deploy config will raise a Capistrano::NoMatchingServersError
with <task> is only run for servers matching {:roles=> <role>}, but no servers matched
. Instead, specify the server-under-test like this:
HOSTFILTER=separate-sidekiq.makandra.de cap productio...
makandra/capybara-lockstep
capybara-lockstep can help you with flaky end-to-end tests:
This Ruby gem synchronizes Capybara commands with client-side JavaScript and AJAX requests. This greatly improves the stability of a full-stack integration test suite, even if that suite has timing issues.
Sending errors to sentry from development
For the initial setup or changes in the sentry reporting it might be useful to enabled reporting of sentry in development. Don't commit these changes and prefer to report to the staging environment. As other developers might be confused of these errors try to given them a proper message and delete them afterwards.
- Add
config.raven_dsn = 'your-dns'
inconfig/environments/development.rb
. - Add development to existing environments in the
Raven.configure
block:config.environments = ['development', 'staging', 'production']
. - ...
Linux: Create file of a given size
Sometimes you need a file of some size (possibly for testing purposes). On Linux, you can use dd
to create one.
Let's say you want a 23 MB file called test.file
. You would then run this:
dd if=/dev/zero of=test.file bs=1048576 count=23
The block size (bs
) is set to 1 MB (1024^2 bytes) here, writing 23 such chunks makes the file 23 MB big.\
Adjust to your needs.
This linux command might also come in handy in a Ruby program. It could be used like:
mb = 23
mb_string, _error_str, _status = Open3.capture3('dd if=/dev/zero...
Geordi 1.2 released
Changes:
-
Remove some old binaries (commands still exist in
geordi
) and mark others as deprecated -
Rewrite deploy command to support most deploy scenarios:
- master to production
- feature branch to staging
- master to staging or production to production (plain deploy)
- Improve Cucumber command (fixes #18):
- Fix pass-through of unknown options to Cucumber
- Add --rerun=N option to rerun failed Cucumber tests up to N times. Reboots the test environment between runs, thus will pick up fixes you made durin...
Selenium: Network throttling via Chromedriver
You can throttle the network in your headless chrome via Selenium. This might be useful for debugging issues with flaky integration tests or slow page simulations.
page.driver.browser.network_conditions = {offline: false, latency: 5, download_throughput: 2 * 1024, upload_throughput: 2 * 1024}
The settings will match to the following UI component in Chrome:
Were the values for the default profiles might match the values from this post:
**S...
Rails: Parsing a time in a desired timezone
Sometimes you want to have a time in a given timezone independent from you Rails timezone settings / system timezone. I usually have this use case in tests.
Example
Time.parse('2020-08-09 00:00')
will return different results e.g. 2020-08-09 00:00:00 +0200
depending on the Rails timezone settings / system timezone. But in this example we always want to have the given time in UTC because that's what the API returns.
it 'returns a valid API response', vcr: true do
expect(client.get('/users/1')).to have_attributes(
name: 'So...
Gemspecs must not list the same gem as both runtime and development dependency
When you're developing a gem, never list the same dependency as both runtime and development dependency in your .gemspec
.
So don't do this:
spec.add_dependency 'activesupport'
spec.add_development_dependency 'activesupport', '~> 2.3'
If you do this, your gemspec will not validate and modern versions of Bundler will silently ignore it. This leads to errors like:
Could not find your-gem-0.1.2 in any of the sources
What to do instead
If you want to freeze a different version of a dependency for your t...
Migrating to Spreewald
This describes how to migrate an existing cucumber test suite to Spreewald.
-
Add the gem
-
Include spreewald into your cucumber environment by putting
require 'spreewald/web_steps'
require 'spreewald/email_steps'
# ...
or just
require 'spreewald/all_steps'
into yoursupport/env.rb
. -
Look through your step definitions for everything that might be included in Spreewald. Candidates are
web_steps
,shared_steps
,table_steps
, `em...
Resolving Element cannot be scrolled into view (Selenium::WebDriver::Error::MoveTargetOutOfBoundsError) on Mavericks
After I upgraded to Mac OS X Mavericks, I regularly got this error message when running Cucumber features with Selenium:
Element cannot be scrolled into view:[object XrayWrapper [object HTMLInputElement]] (Selenium::WebDriver::Error::MoveTargetOutOfBoundsError)
I had the Terminal window running the test on my secondary screen, whereas the Selenium-webdriven Firefox always started on my primary one. Now if I had focused the secondary screen when running the tests, Selenium could not start Firefox and switch to it (probably because t...
How to communicate between processes in Ruby with sockets
In Ruby you can communicate between processes with sockets. This might be helpful in tests that validate parallel executions or custom finalization logic after the garbage collector. Here is an example how such an communication will look like:
require 'socket'
BUFFER_SIZE = 1024
# DGRAM has the advantage that it stops reading the pipe if the next messages starts. In case the message size is larger than the
# BUFFER_SIZE, you need to handle if you are reading another part of the current message or if you already reading the
# next mess...
Silence specific deprecation warnings in Rails 3+
Sometimes you're getting an ActiveSupport deprecation warning that you cannot or don't want to fix. In these cases, it might be okay to silence some specific warnings. Add this to your initializers, or require it in your tests:
silenced = [
/Not considered a useful test/,
/use: should(_not)? have_sent_email/,
] # list of warnings you want to silence
silenced_expr = Regexp.new(silenced.join('|'))
ActiveSupport::Deprecation.behavior = lambda do |msg, stack|
unless msg =~ silenced_expr
ActiveSupport::Deprecation::DEFAULT_BEHAVI...