cucumber / cucumber-ruby

Cucumber for Ruby. It's amazing!

Home Page:https://cucumber.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Namespaced worlds become nil after first scenario runs

mitchgrout opened this issue · comments

Describe the bug
When namespaced Worlds are used, after the first scenario runs all namespaced worlds change from being a valid Object to being nil. This has no impact on pure methods, but stateful methods (e.g. attr_accessor) break in unclear ways. This is currently not caught by the worlds test suite (features/docs/writing_support_code/world.feature), as it does not test stateful methods, nor multi-scenario features.

To Reproduce
Steps to reproduce the behavior:

  1. Create a step definition file containing the following definitions:
module ModuleOne
  attr_accessor :state
  def pure
    42
  end
end

World(module_one: ModuleOne)

Then /^module one is not nil$/ do
  expect(module_one).not_to be nil
end

Then /^state can be set$/ do
  expect { module_one.state = 42 }.not_to raise_error
end

Then /^pure method can be called$/ do
  expect { module_one.pure }.not_to raise_error
end
  1. Create a feature file containing the following scenarios:
Feature: Multi-scenario namespaced worlds which keep state state                
  Scenario: 1. All checks pass
    Then module one is not nil 
    Then state can be set 
    Then pure method can be called
  
  Scenario: 2. Namespaced world becomes nil
    Then module one is not nil

  Scenario: 3. Stateful methods do not work
    Then state can be set 

  Scenario: 4. Pure methods still work
    Then pure method can be called
  1. Run the tests, and observe the results for the following scenarios:
    • 1. All checks pass passes
    • 2. Namespaced world becomes nil fails with expected not #<NilClass:8> => nil got #<NilClass:8> => nil
    • 3. Stateful methods do not work fails with expected no Exception, got #<FrozenError: can't modify frozen NilClass: nil>
    • 4. Pure methods still work passes

Expected behavior
It is expected that all scenarios pass, regardless of execution order, as well as that namespaced worlds can maintain state per-scenario, via standard Ruby patterns such as attr_accessor.

Context & Motivation
Looking at using namespaced worlds to provide an up-front declaration of my test suites state, rather than relying on instance variables.

Screenshots
N/A

Your Environment

  • ruby-cucumber, v7.1.0 & main (a7e6855)
  • ruby, v3.0.0
  • Ubuntu 20.04 LTS, running kernel 5.4.0

Additional context
This issue does not appear to be a problem with non-namespaced worlds, i.e. both scenarios would run if World(ModuleOne) was used, and the module_one. namespace dropped from the step definitions.

The behaviour can be resolved if all namespaced modules are explicitly undefined after each scenario (i.e. via an After hook). This was traced back to lib/cucumber/glue/proto_world.rb in add_namespaced_modules!, lines 189 - 193:

                inner_world = if self.class.respond_to?(namespace)                                                                                                                                                 
                                instance_variable_get(variable_name)
                              else
                                Object.new
                              end

It can also be resolved if the conditional is replaced with just Object.new.

I believe that after the first scenario executes, the instance variable "@__#{namespace}_world" is set to nil alongside all other global state, but is not reinitialized to a new valid Object. The modules methods are then defined on nil, which causes stateful methods to fail since nil is frozen.

Nice catch, and thanks for the detailed report! 🤩