Update: Aggregated RSpec/Cucumber test coverage with RCov

Our rcov:all task for aggregated RSpec/Cucumber coverage was overhauled extensively. Among other things it now works for Rails 2 and 3 and has an option to ignore shared traits.

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.

Test if a checkbox is checked in jQuery

jqueryElement.is(':checked')

7 Fresh and Simple Ways to Test Cross-Browser Compatibility | Freelance Folder

In this article we’ve listed 7 fresh and simple tools for cross-browser compatibility testing, tools that actually make this stuff pretty easy. Not only that, but every single one of these tools can be used for free.

Celerity | Easy and fast functional test automation for web applications

Celerity is a JRuby wrapper around HtmlUnit – a headless Java browser with JavaScript support. It provides a simple API for programmatic navigation through web applications. Celerity aims at being API compatible with Watir.

Ultimate rspec matcher to test named_scope or scoped - Web development blog

What do we expect from the custom finder? We expect that it should find assets A, B, C and should not find assets D, E, F. And sometimes the order is important: it should find A, B C with exact order.

How to test inside iframes with cucumber / capybara

When testing with Cucumber / Caypbara, iframes are ignored, so you can't interact with them.

To interact with your iframe, you have to explicitly tell your driver to use it.
You can reference your iframe either through it's id, or if none given, by it's number:

When /^(.*?) inside the (.*?). iframe$/ do |nested_step, frame_number|
  page.within_frame(frame_number.to_i) do
    step nested_step
  end
end

When /^(.*?) inside the (.*?). iframe:$/ do |nested...

Geordi: Running Selenium tests in a VNC buffer

Geordi now supports our solution for running Selenium tests without having Firefox or Chrome windows popping up all over your workspace.

This will stop Selenium windows from appearing on your desktop, but you can still inspect them when necessary.

Installation

Update geordi with gem install geordi.

Run geordi vnc --setup and follow the instructions.

Usage

geordi cucumber will automatically use VNC. Launchy will still open pages in the usual place.

geordi vnc will allow...

Jasmine: Mocking ESM imports

In a Jasmine spec you want to spy on a function that is imported by the code under test. This card explores various methods to achieve this.

Example

We are going to use the same example to demonstrate the different approaches of mocking an imported function.

We have a module 'lib' that exports a function hello():

// lib.js

function hello() {
  console.log("hi world")
}

export hello

We have a second module 'client' that exports a function helloTwice(). All this does is call hello() ...

Testing ActiveRecord validations with RSpec

Validations should be covered by a model's spec.

This card shows how to test an individual validation. This is preferrable to save an entire record and see whether it is invalid.

Recipe for testing any validation

In general any validation test for an attribute :attribute_being_tested looks like this:

  1. Make a model instance (named record below)
  2. Run validations by saying record.validate
  3. Check if record.errors[:attribute_being_tested] contains the expected validation error
  4. Put the attribute into a valid state
  5. Run...

Rails: Fixing ETags that never match

Every Rails response has a default ETag header. In theory this would enable caching for multiple requests to the same resource. Unfortunately the default ETags produced by Rails are effectively random, meaning they can never match a future request.

Understanding ETags

When your Rails app responds with ETag headers, future requests to the same URL can be answered with an empty response if the underlying content ha...

How to disable Chrome's save password bubble for Selenium tests

When filling out forms in Selenium tests, Chrome shows the (usual) bubble, asking to store those credentials.

While the bubble does not interfere with tests, it is annoying when debugging tests. Here are two ways to disable it:

Option 1: prefs

You can set profile preferences to disable the password manager like so:

prefs = {
  'credentials_enable_service' => false,
  'profile.password_manager_enabled' => false
}

Capybara::Selenium::Driver.new(app, browser: :chrome, prefs: prefs)

Sadly, there are no command line s...

Jasmine: Creating DOM elements efficiently

Jasmine specs for the frontend often need some DOM elements to work with. Because creating them is such a common task, we should have an efficient way to do it.

Let's say I need this HTML structure:

<ul type="square">
  <li>item 1</li>
  <li>item 2</li>
</ul>

This card compares various approaches to fabricating DOM elements for testing.

Constructing individual elements

While you can use standard DOM functions to individually create and append elements, this is extremely verbose:

let list = document.createElement('...

JavaScript: Testing the type of a value

Checking if a JavaScript value is of a given type can be very confusing:

  • There are two operators typeof and instanceof which work very differently.
  • JavaScript has some primitive types, like string literals, that are not objects (as opposed to Ruby, where every value is an object).
  • Some values are sometimes a primitive value (e.g. "foo") and sometimes an object (new String("foo")) and each form requires different checks
  • There are three different types for null (null, undefined and NaN) and each has different rules for...

How to tackle complex refactorings in big projects

Sometimes huge refactorings or refactoring of core concepts of your application are necessary for being able to meet new requirements or to keep your application maintainable on the long run. Here are some thoughts about how to approach such challenges.

Break it down

Try to break your refactoring down in different parts. Try to make tests green for each part of your refactoring as soon as possible and only move to the next big part if your tests are fixed. It's not a good idea to work for weeks or months and wait for all puzzle pieces ...

How to make changes to a Ruby gem (as a Rails developer)

At makandra, we've built a few gems over the years. Some of these are quite popular: spreewald (> 1M downloads), active_type (> 1M downloads), and geordi (> 200k downloads)

Developing a Ruby gem is different from developing Rails applications, with the biggest difference: there is no Rails. This means:

  • no defined structure (neither for code nor directories)
  • no autoloading of classes, i.e. you need to require all files yourself
  • no active_support niceties

Also, their scope...

Testing shared traits or modules without repeating yourself

When two classes implement the same behavior (methods, callbacks, etc.), you should extract that behavior into a trait or module. This card describes how to test that extracted behavior without repeating yourself.

Note that the examples below use Modularity traits to extract shared behavior. This is simply because we like to do it that way at makandra. The same techniques apply for modules and overriding self.included.

Example
---...

Defining and calling lambdas or procs (Ruby)

Ruby has the class Proc which encapsulates a "block of code". There are 2 "flavors" of Procs:

  • Those with "block semantics", called blocks or confusingly sometimes also procs
  • Those with "method semantics", called lambdas

lambdas

They behave like Ruby method definitions:

  • They are strict about their arguments.
  • return means "exit the lambda"

How to define a lambda

  1. With the lambda keyword

    test = lambda do |arg|
      puts arg
    end
    
  2. With the lambda literal -> (since Ruby 1.9.1)
    ...

Disable text-transforms in Selenium tests

Using text-transform: uppercase - especially on form labels - can cause you serious headaches in Selenium tests. Sometimes the web driver will see the uppercase text, sometimes it won't, and umlauts will be a problem as well.

Simply disable it in tests, by

  • adding a body class for tests

    %body{'data-environment' => Rails.env}
    
  • overriding the transforms

    [data-environment="test"] *
      text-transform: none !important
    

How the Date Header Affects Cookie Expiration and Caching

tl;dr

When a cookie includes an Expires attribute or an HTTP response includes caching headers like Expires or Cache-Control, their validity depends on the server's Date header if present. Otherwise, the browser uses its local time. This can lead to issues in tests with mocked time or inconsistent cache behavior.

Cookie Expires depends on the Date header or browser time

When a cookie includes an Expires attribute, the browser evaluates the expiration date relative to a reference time:

  1. If the HTTP response ...

Custom error messages in RSpec or Cucumber steps

Sometimes you have a test expectation but actually want a better error message in case of a failure. Here is how to do that.

Background

Consider this test:

expect(User.last).to be_present

In case of an error, it will fail with a not-so-helpful error message:

expected present? to return true, got false (Spec::Expectations::ExpectationNotMetError)

Solution

That can be fixed easily. RSpec expectations allow you to pass an error message like this:

expect(User.last).to be_present, 'Could not find a user!'

...

Rails: Testing file downloads with request specs

tl;dr

Prefer request specs over end-to-end tests (Capybara) to joyfully test file downloads!

Why?

Testing file downloads via Capybara is not easy and results in slow and fragile tests. We tried different approaches and the best one is just okay.

Tests for file downloads via Capybara ...

  • ... are slow,
  • ... are fragile (breaks CI, breaks if Selenium driver changes, ...),
  • ... need workarounds for your specia...