1 min read

Controlling State Variables in Cucumber

Cucumber steps love local variables as the way the transition state across steps. Problem is that they're a rich source of bugs in your test suite. Let's look at a faked example.

Feature: Upload a photo

  Scenario: Set my avatar picture
    Given my idenity is verified
    When I upload an image
    Then it will become my avatar photo

Hmm, so to do this I'm going to make a couple of HTTP requests. I'll verify myself, then upload the image but I'll write a couple of naïve tests.

Given("my identity is verified") do
  @result = IdentiyService.authorise(@user)
end

When("I upload an image") do
  @resutl = ImageService.upload(@photo, authenticated_by: @result.token)
end

Then("it will become my avatar photo") do
  expect(@result.code).to be 200
end

So there's loads of problems here but let's look specifically at the state variables, those Ruby variables prefixed with @.

  1. There's meant to be a @user variable. Is it initialised? Is it correctly set?
  2. We're re-using a @result variable, is this going to screw up later tests when more steps are added (oh yeah).
  3. Oh, and clearly the first @result is a different thing from the second because we're expecting a #token method on it.
  4. There's clearly a mis-spelling of @result so the assertion step isn't going to test the right variable but the interpreter will happily ignore the problem.

Let's make something better.

Struct.new('TestArtefacts', :user, :result, :auth) do
  def initialize(*)
    super(*)
    self.user = User.new
    self.photo = Photo.new
  end
end

def artefacts
  @test_artefacts ||= Struct::TestArtefacts.new
end
data_helpers.rb

So now I have a lightweight struct a lazy initializer, and a default value for the user object. I can change my steps to:

Given('my idenity is verified') do
  artefacts.auth = IdentityService.authorise(artefacts.user)
end

When('I upload an image') do
  artefacts.result = ImageService.upload(artefacts.photo, authenticated_by: artefacts.auth.token)
end

Then("it will become my avatar photo") do
  expect(artefacts.result.code).to be 200
end

Any typo we make in assigning a variable will be caught by the interpreter. If we need to, we can add custom getters and setters to the struct.