The other day I had trouble getting a Cucumber scenario to work. Here’s what happened, partly to have a post I can come back to, when (!) my future self runs into a similar problem.
I am using Cucumber
and a very stripped down version of the project looked like this:
% tree
.
└── features
├── example.feature
├── step_definitions
│ └── step_def.rb
└── support
└── env.rb
All code is entirely contrived and tailored to demonstrate the issue I bumped into. The example file features/example.feature
first:
Feature: What is happening?
Scenario Outline: Matching step definitions -- or not
Given a step that mentions '<a_string>'
And another step that uses <a_number> like this
Then all is good
Examples:
| a_string | a_number |
| word | 42 |
| two words | 3.1415927 |
There are very plain step definitions too (in file step_definitions/step_def.rb
):
Given("a step that mentions {string}") do |string|
pending # Write code here …
end
Given(/another step that uses ((\d+)|(\d+\.\d+)) like this/) do |number|
pending # Write code here …
end
Then("all is good") do
pending # Write code here …
end
Running this gives the expected result: Cucumber
kindly informs that there are pending
steps:
% bundle exec cucumber
Feature: What is happening?
Scenario Outline: Matching step definitions -- or not # features/example.feature:3
Given a step that mentions '<a_string>' # features/example.feature:5
And another step that uses <a_number> like this # features/example.feature:6
Then all is good # features/example.feature:7
Examples:
| a_string | a_number |
| word | 42 |
| two words | 3.1415927 |
2 scenarios (2 pending)
6 steps (4 skipped, 2 pending)
0m0.012s
However at some point, I started getting another result (comments added by cucumber removed):
% bundle exec cucumber
Feature: What is happening?
Scenario Outline: Matching step definitions -- or not
Given a step that mentions '<a_string>'
And another step that uses <a_number> like this
Then all is good
Examples:
| a_string | a_number |
| word | 42 |
| two words | 3.1415927 |
2 scenarios (2 undefined)
6 steps (4 skipped, 2 undefined)
0m0.009s
You can implement step definitions for undefined steps with these snippets:
Given("a step that mentions {string}") do |string|
pending # Write code here that turns the phrase above into concrete actions
end
Wait. What?!? I stared at the existing step definition for a while, comparing it with the one printed in the message above:
Given("a step that mentions {string}") do |string|
pending # Write code here …
end
Confusion and disbelief kicked in.
One of the principles I use is this:
The computer is always right.
— Not sure where I picked this up. If you know the (or a) source, tell me please.
This is true even if the behaviour is wrong. In this case: If cucumber
cannot find a step definition … it CANNOT find a step definition. But why would that be? Why did it happen in this particular case?
I even called in colleagues (remotely) and we stared at the code collectively. Still nothing.
Luckily a trace of a previous successful run was still available in the console output. So I copy-and-pasted the scenarios, and compared them piece by piece in a Pry session:
% pry
[1] pry(main)> works = File.read 'features/works.feature'
=> "Feature: …"
[2] pry(main)> broken = File.read 'features/broken.feature'
=> "Feature: …"
[3] pry(main)> works == broken
=> false
So there is in fact a difference. But what? Where?
Using the same pry
session we found out:
[4] pry(main)> works.each_codepoint.zip(broken.each_codepoint).select{|el| el[0] != el[1] }
=> [
[0] [
[0] 32,
[1] 160
]
]
What this does: The codepoints
of both strings are combined in pairs, and then the pairs that are different are select
ed. Here’s what the Ruby Documentation says about each_codepoint
:
Passes the Integer ordinal of each character in str, also known as a codepoint when applied to Unicode strings to the given block. For encodings other than UTF-8/UTF-16(BE|LE)/UTF-32(BE|LE), values are directly derived from the binary representation of each character. If no block is given, an enumerator is returned instead.
➙ https://ruby-doc.org/core-2.7.1/String.html#method-i-each_codepoint
“hello\u0639”.each_codepoint {|c| print c, ‘ ‘ }
produces:
104 101 108 108 111 1593
The small piece that changed the behaviour was a space, just not a ‘normal’ space, but a NO-BREAK SPACE
(see, for example, https://en.wikipedia.org/wiki/Non-breaking_space for more about this topic).
Lessons I learned (again):
- Some problems are hard to see, and in this case it was even invisible.
- The message was correct: The step was not defined, it only looked (to the human eye) as if it was.
- Using <spacebar> gives a different result than <option>-<sapacebar>.