How to achieve more clean, encapsulated, modular step definitions with Spinach

Spinach is a new awesome BDD framework that features encapsulation and modularity of your step definitions.

We've been using Cucumber for a while now, and we must say we fell in love with it from the first moment.

But not anymore: It broke our hearts.

You know, we realized what we really loved about Cucumber wasn't Cucumber itself but Gherkin. Gherkin is the feature parser behind it and has some really nice features:

  • A really natural DSL (as natural as it can be)
  • A simple way to abstract a feature description
  • It helps you focus on business value, even if there's no real impact on the actual execution (In order to...)

But what sucks about cucumber?

Step MADNESS

Where exactly should I put my step definitions? What if they're corelated - can I abstract them in a simple way? Can I reuse them across projects? Could I even test them?

With cucumber, you usually run into this kind of situations and there's no easy way to get over it. You should use better step file namings perhaps? Create some methods that live next to each other in the cucumber World? Nah, it just doesn't feel good.

Step ambiguity

Sharing steps between all the features in your project just doesn't scale. It's fine if you have a couple of features, but you start hitting ambiguous steps when it grows off a certain limit.

And when that happens, you must write them taking in account the ones written before, and just having your mind somewhere else misses the whole point of writing them - you should focus on your feature

Regexp-based step matching

Reusable Cucumber steps == ugly steps. If you want a to reuse the same step between multiple situations, either you're going too far in the integration tests (and thus you should be doing that in the unit tests) or you should be writing some helper methods.

It just doesn't feel good not to have explicitly defined what step is being executed. It's like metaprogramming: it can be useful, but most of the times is a bad habit.

So, for that... we made Spinach

Spinach focuses on step reusability and encapsulation so you can reuse them in a clean way across features and projects.

  • Features are just Ruby classes
  • Leverages Gherkin parser
  • Steps are just Ruby methods
  • Supports MiniTest and RSpec as well as Capybara

Fighting step madness and ambiguity

  • Each feature has its own steps (so no more global steps)
  • Explicit reusability through Ruby mixins

Simple architecture

  • Small codebase
  • Fully documented
  • Simple hooks system

Show me an example

Given this feature

Feature: Test how spinach works  
  In order to know what the heck is spinach
  As a developer
  I want it to behave in an expected way

  Scenario: Formal greeting
    Given I have an empty array
    And I append my first name and my last name to it
    When I pass it to my super-duper method
    Then the output should contain a formal greeting

  Scenario: Informal greeting
    Given I have an empty array
    And I append only my first name to it
    When I pass it to my super-duper method
    Then the output should contain a casual greeting

This is how its Spinach feature steps file looks

class TestHowSpinachWorks < Spinach::FeatureSteps  
  Given 'I have an empty array' do
    @array = Array.new
  end

  And 'I append my first name and my last name to it' do
    @array += ["John", "Doe"]
  end

  When 'I pass it to my super-duper method' do
    @output = capture_output do
      Greeter.greet(@array)
    end
  end

  Then 'the output should contain a formal salutation' do
    @output.must_include "Hello, mr. John Doe"
  end

  And 'I append only my first name to it' do
    @array += ["John"]
  end

  Then 'the output should contain a casual salutation' do
    @output.must_include "Yo, John! Whassup?"
  end

  private

  def capture_output
    out = StreamIO.new
    $stdout = out
    $stderr = out
    yield
    $stdout = STDOUT
    $stderr = STDERR
    out.string
  end
end  

Ruby compatibility

Spinach runs on MRI 1.9 and Rubinius/JRuby support is on the works.

Note that not giving support for MRI 1.8.7 is a purposeful choice and not a negligence. This is new software, why should we encourage using legacy versions?

So if you wanna give it a try, here's all you need:

We would really love some feedback. Hope you like it!