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>.