kaleidic kaleidic - 2 months ago 4
Ruby Question

What object is in play for instance variables (i.e. what is self) in Cucumber step definitions?

I'm not understanding scoping when using Cucumber in Ruby, especially with regard to instance variables.

For the context of my immediate example, in the Before portion of

the variable
is assigned.

@browser = Watir::Browser.new @browser_selected.to_sym

is usually 'chrome')

In step definitions @browser gets used. As a simple example:

What I don't understand is what object contains @browser as an attribute. How does it have meaning in this context? I know the code I'm puzzled by is always in a block, and I recognize that each such block is used (via the Given/When/Then message to which it is attached) to be pre-processed in some mysterious way.

Amidst this shrouding in mystery is the scoping of instance variables. How can I know the scope of instances variables within such blocks?


self in Cucumber steps and hooks is just a Ruby object, the "world", which is used throughout each scenario. The block in each step definition is executed in the context of the world with the_world.instance_eval or something similar, meaning that when each block runs self is the world. So the object to which all of those instance variables belong is the same object, the world. The scope of all of those instance variables is the entire scenario.

So it's important to use instance variables sparingly in Cucumber steps, and to make it clear in step names that you're using them (that is, make it clear in step names that they refer to some piece of state). These steps clearly refer to a thing which is saved behind the scenes (i.e. refer to the same instance variable):

Given there is a thing
When I frob the thing
Then the thing should be frobbed

That's fine and normal. But it would be terrible if When I frob the thing precalculated some expected assertion results and stashed them in instance variables too, and Then the thing should be frobbed used those instance variables in its assertions. Then the thing should be frobbed would not work unless When I frob the thing preceded it, making it less reusable, and that restriction would not be obvious to others writing features and they would become frustrated. (Don't be like my former colleague.)

Back to the world: Cucumber makes a new world for each scenario and throws it away at the end so a scenario's instance variables don't affect the next scenario. In plain Cucumber, the world is just an instance of Object. In cucumber-rails, it's an instance of Cucumber::Rails::World (which is interesting to read). Aside from the methods built in to the world in cucumber-rails, the world gets its methods by extending modules (as in the_world.extend SomeModule). As with instance variables, all the methods from all the modules that the world extends are jammed on to the same object (the world), so you sometimes need to worry about name conflicts.