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 @
.
- There's meant to be a
@user
variable. Is it initialised? Is it correctly set? - We're re-using a
@result
variable, is this going to screw up later tests when more steps are added (oh yeah). - Oh, and clearly the first
@result
is a different thing from the second because we're expecting a#token
method on it. - 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
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
.
Member discussion