FASTERAGILE

Highlighting Undefined Cucumber Step Definitions With Syntastic in Vim

I enjoy writing code using BDD by starting from a user story and writing a cucumber feature to define what I want to see on the page. From there I drop down several levels from Cucumber to RSpec and back again as I go.

When creating a new cucumber feature using declarative steps, it’s normal to frequently have undefined steps. It’d be helpful if our editor could show us which steps are already matched (and thus defined in our step definitions) and which steps are undefined as a sort of progress bar to track feature step implementation.

This is possible with Vim and Syntastic. I’ll provide details about how to get this all working in the context of a Rails 4 application with Ruby 2.2.1.

Step 1

Alter your cucumber.yml file to include a syntastic profile and use the -r flag to require the files contained within the step_definitions directory.

1
2
3
4
5
6
7
8
9
<%
rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --tags ~@wip --snippet-type percent"
%>
syntastic: -r features/step_definitions <%= std_opts %> CUCUMBER_PROFILE='syntastic'
default:   -r features                  <%= std_opts %>
wip:       -r features                  --tags @wip:3 --wip
rerun:     -r features                  <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip --snippet-type percent

Note: if your feature files are in a non-standard folder (features/steps perhaps), be sure to use that folder name instead of features/step_definitions

Step 2

Setup Vim to use the newly added syntastic cucumber profile in your .vimrc:

1
2
3
4
5
let g:syntastic_mode_map = { 'mode': 'active' }
let g:syntastic_ruby_checkers = ['mri', 'rubocop']
let g:syntastic_cucumber_cucumber_args="--profile syntastic"
let g:syntastic_warning_symbol = "⚠"
" let g:syntastic_debug = 1

syntastic_ruby_checkers specifies both mri and rubocop, which I highly recommend. If you don’t want to use rubocop, just remove it from the array.

The last option is commented out, but is useful if syntastic isn’t working properly. Just uncomment it, then save a feature file, and check the messages output from vim in Command-line mode with :messages. This will show you the cucumber command that syntastic just ran. Executing the command that syntastic tried to run yourself at the command line is a great troubleshooting resource if something isn’t working right. Here’s what syntastic should be trying to do at this point when running a single feature file.

1
cucumber --profile syntastic --dry-run --quiet --strict --format pretty features/creating_a_new_company.feature

If you don’t use any gems that define methods that need to be available in step definitions then you’re done!

Working with gems that define methods that implement regex patterns

If you use the pickle gem or another gem that defines methods to help with regex matching in step definitions, then you’ll need to add a few more tweaks to get proper undefined step status reported from syntastic.

Notice the env var specified in the above cucumber.yml file, CUCUMBER_PROFILE='syntastic'. This is so that we can conditionally require some extra code in a few step definition files that need methods defined from within the pickle gem to be available.

The “highlight undefined steps” feature of syntastic takes advantage of the ability for cucumber to do a ‘dry run’ where it doesn’t actually execute any of the code inside of the steps. It will, however, let you know if a step is undefined. One caveat with --dry-run is that it doesn’t load the rails environment, so the pickle steps that need to reach into the gem for some regex matching will be shown as undefined. Initially I took the path of just defining these methods inline in the file like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
if ENV['PROFILE'] == 'syntastic'
  # allow cucumber --dry-run to execute (env.rb isn't loaded), so these methods
  # defined in the pickle gem won't be found. Define them here with a very
  # generic capture-all regex. Doing this also requires the --guess flag be
  # added to the syntastic profile since these matchers make things a litte
  # more generic than the actual gem versions
  def capture_email;          '(.+)'; end
  def capture_model;          '(.+)'; end
  def capture_fields;         '(.+)'; end
  def capture_plural_factory; '(.+)'; end
  def capture_predicate;      '(.+)'; end
  def capture_value;          '(.+)'; end
end

However, the --guess flag is required for cucumber with this setup. That proved to be problematic and I had a lot of false positives that matched when they shouldn’t have.

Since the rails environment isn’t loaded we need to manually require the necessary files to allow our pickle steps to call their gem’s methods.

Step 3

Create a features/support/pickle_dry_run.rb with the following:

1
2
3
4
5
6
7
8
9
10
module PickleDryRun
  pickle_path = Bundler.rubygems.find_name('pickle').first.full_gem_path
  require "#{pickle_path}/lib/pickle"
  require "#{pickle_path}/lib/pickle/parser/matchers"
  require "#{pickle_path}/lib/pickle/email/parser"
  include Pickle
  def config; Pickle.config; end
  include Pickle::Parser::Matchers
  include Pickle::Email::Parser
end

Step 4

In the pickle_steps.rb file add this to the top:

1
2
3
4
if ENV['CUCUMBER_PROFILE'] == 'syntastic'
  require_relative '../../support/pickle_dry_run'
  include PickleDryRun
end

This will conditionally require and includes the PickleDryRun module during a syntastic check so that the steps in pickle_steps.rb can call out to the methods contained within the pickle gem.

If you generated the pickle email steps, then we need to add something similar. The top of my email_steps.rb looks like this:

1
2
3
4
5
6
7
8
9
10
11
if ENV['CUCUMBER_PROFILE'] == 'syntastic'
  require_relative '../../support/pickle_dry_run'
  include PickleDryRun
else
  ActionMailer::Base.delivery_method = :test
  ActionMailer::Base.perform_deliveries = true

  Before do
    ActionMailer::Base.deliveries.clear
  end
end

Step 5

To allow for factory_girl to work properly with pickle inside of a dry-run triggered by syntastic, we need to execute cucumber with spring.

Luckily, the ability to change the syntastic cucumber executable exists and is easily configurable. Just beneath the syntastic_cucumber_cucumber_args specify this in your .vimrc:

1
let g:syntastic_cucumber_cucumber_exe='bin/cucumber'

This will use the cucumber binstub which relies on spring having already loaded the rails environment. The first request can be slow-ish, since spring has to spin up rails (if it hasn’t yet), but subsequent executions are very fast.

Just make sure that there is a cucumber binstub in the bin folder at the root of the rails app. If there isn’t, be sure to check out spring’s setup guide which mentions the need to run bundle exec spring binstub --all. The spring-commands-cucumber gem is also required as noted in the spring readme.

With all of this in place, things should be working properly. As with most things vim though, closing completely out re-launching vim is usually necessary.

Now if you turn syntastic debug mode on, it’ll show cucumber executing from the binstub. Nice!

1
bin/cucumber --profile syntastic --dry-run --quiet --strict --format pretty features/creating_a_new_company.feature

Next time you create an undefined step and save your feature file, it’ll look like this!

Comments