[cucumber-jvm] Why are we not allowed to extend classes that contain Cucumber hooks?

20,085 views
Skip to first unread message

Christian

unread,
Oct 19, 2014, 6:38:02 AM10/19/14
to cu...@googlegroups.com
Hi all,

When trying to extend a class that uses cucumber hooks, Cucumber throws the following error message:
cucumber.runtime.CucumberException: You're not allowed to extend classes that define Step Definitions or hooks

What's the rationale behind stopping us from doing that?

Brgrds,

Christian

aslak hellesoy

unread,
Oct 19, 2014, 6:51:25 AM10/19/14
to Cucumber Users
Cucumber creates a new instance of all classes defining stepdefs before each scenario.
It then invokes stepdef methods on *one* of those instances whenever it needs to run a step.

If you defined a stepdef method foo in class A and you have a class B extends A you'd get an a and b instance.
The foo method would be available on both instances, and Cucumber would not be able to decide what instance to invoke the method on.

That's why we don't allow it.

The solution is to use composition instead of inheritance. You can achieve composition with dependency injection - Cucumber supports several popular DI frameworks.

Cheers,
Aslak
 
Brgrds,

Christian

--
Posting rules: http://cukes.info/posting-rules.html
---
You received this message because you are subscribed to the Google Groups "Cukes" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cukes+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Christian

unread,
Oct 19, 2014, 2:34:08 PM10/19/14
to cu...@googlegroups.com


On Sunday, October 19, 2014 11:51:25 AM UTC+1, Aslak Hellesøy wrote:
On Sun, Oct 19, 2014 at 11:38 AM, Christian <christia...@gmail.com> wrote:
Hi all,

When trying to extend a class that uses cucumber hooks, Cucumber throws the following error message:
cucumber.runtime.CucumberException: You're not allowed to extend classes that define Step Definitions or hooks

What's the rationale behind stopping us from doing that?


Cucumber creates a new instance of all classes defining stepdefs before each scenario.
It then invokes stepdef methods on *one* of those instances whenever it needs to run a step.

If you defined a stepdef method foo in class A and you have a class B extends A you'd get an a and b instance.
The foo method would be available on both instances, and Cucumber would not be able to decide what instance to invoke the method on.

My A is abstract and only holds Before and After hooks, not step defs, anyway, so there is little chance of invoking foo there. Plus, if you have a C extends B extends A chain, couldn't you just invoke the foo method where it is highest up the chain, i.e. in C?

Brgrds,
Christian
 
That's why we don't allow it.

The solution is to use composition instead of inheritance. You can achieve composition with dependency injection - Cucumber supports several popular DI frameworks.
I'm trying to plug Cucumber into a project that has grown over many years. It's hard enough to get management's OK for that. If I now try to introduce something anywhere near as complex as Spring, for example, I'll most likely not get support for it. Is there a really lightweight DI framework you'd recommend?

aslak hellesoy

unread,
Oct 19, 2014, 3:26:14 PM10/19/14
to Cucumber Users
On Sun, Oct 19, 2014 at 7:34 PM, Christian <christia...@gmail.com> wrote:


On Sunday, October 19, 2014 11:51:25 AM UTC+1, Aslak Hellesøy wrote:
On Sun, Oct 19, 2014 at 11:38 AM, Christian <christia...@gmail.com> wrote:
Hi all,

When trying to extend a class that uses cucumber hooks, Cucumber throws the following error message:
cucumber.runtime.CucumberException: You're not allowed to extend classes that define Step Definitions or hooks

What's the rationale behind stopping us from doing that?


Cucumber creates a new instance of all classes defining stepdefs before each scenario.
It then invokes stepdef methods on *one* of those instances whenever it needs to run a step.

If you defined a stepdef method foo in class A and you have a class B extends A you'd get an a and b instance.
The foo method would be available on both instances, and Cucumber would not be able to decide what instance to invoke the method on.

My A is abstract and only holds Before and After hooks, not step defs, anyway, so there is little chance of invoking foo there. Plus, if you have a C extends B extends A chain, couldn't you just invoke the foo method where it is highest up the chain, i.e. in C?


What if your A has two direct descendants, B1 and B2. On what instance would you expect the hooks to be invoked?

Why do you think you need inheritance in the first place?

Cheers,
Aslak

Paolo Ambrosio

unread,
Oct 19, 2014, 3:27:03 PM10/19/14
to cu...@googlegroups.com
On Sun, Oct 19, 2014 at 7:34 PM, Christian <christia...@gmail.com> wrote:
>
>
> On Sunday, October 19, 2014 11:51:25 AM UTC+1, Aslak Hellesøy wrote:
>>
>> On Sun, Oct 19, 2014 at 11:38 AM, Christian <christia...@gmail.com> wrote:
>>>
>>> Hi all,
>>>
>>> When trying to extend a class that uses cucumber hooks, Cucumber throws
>>> the following error message:
>>> cucumber.runtime.CucumberException: You're not allowed to extend classes
>>> that define Step Definitions or hooks
>>>
>>> What's the rationale behind stopping us from doing that?
>>>
>>
>> Cucumber creates a new instance of all classes defining stepdefs before
>> each scenario.
>> It then invokes stepdef methods on *one* of those instances whenever it
>> needs to run a step.
>>
>> If you defined a stepdef method foo in class A and you have a class B
>> extends A you'd get an a and b instance.
>> The foo method would be available on both instances, and Cucumber would
>> not be able to decide what instance to invoke the method on.
>
>
> My A is abstract and only holds Before and After hooks, not step defs,
> anyway, so there is little chance of invoking foo there.

Hooks are invoked in a similar way to step definitions.

> Plus, if you have a
> C extends B extends A chain, couldn't you just invoke the foo method where
> it is highest up the chain, i.e. in C?

What if both C and D extend B that extends A?

> Brgrds,
> Christian
>
>>
>> That's why we don't allow it.
>>
>> The solution is to use composition instead of inheritance. You can achieve
>> composition with dependency injection - Cucumber supports several popular DI
>> frameworks.
>
> I'm trying to plug Cucumber into a project that has grown over many years.
> It's hard enough to get management's OK for that. If I now try to introduce
> something anywhere near as complex as Spring, for example, I'll most likely
> not get support for it. Is there a really lightweight DI framework you'd
> recommend?

PicoContainer is a very simple DI framework that requires no configuration.

Consider buying "The Cucumber for Java Book" (in beta at the moment)
that has answers to the most common questions:

https://pragprog.com/book/srjcuc/the-cucumber-for-java-book

This is an excerpt from the chapter on Dependency Injection:

http://media.pragprog.com/titles/srjcuc/simplify.pdf


Paolo

Christian

unread,
Oct 21, 2014, 8:26:00 AM10/21/14
to cu...@googlegroups.com
I'd expect the same error I'd get now when I have a duplicate step definition...
 
> Brgrds,
> Christian
>
>>
>> That's why we don't allow it.
>>
>> The solution is to use composition instead of inheritance. You can achieve
>> composition with dependency injection - Cucumber supports several popular DI
>> frameworks.
>
> I'm trying to plug Cucumber into a project that has grown over many years.
> It's hard enough to get management's OK for that. If I now try to introduce
> something anywhere near as complex as Spring, for example, I'll most likely
> not get support for it. Is there a really lightweight DI framework you'd
> recommend?

PicoContainer is a very simple DI framework that requires no configuration.

Consider buying "The Cucumber for Java Book" (in beta at the moment)
that has answers to the most common questions:

https://pragprog.com/book/srjcuc/the-cucumber-for-java-book

This is an excerpt from the chapter on Dependency Injection:

http://media.pragprog.com/titles/srjcuc/simplify.pdf


Paolo

 Thanks, I'll have a look...

Brgrds,
Christian

Christian

unread,
Oct 21, 2014, 8:38:24 AM10/21/14
to cu...@googlegroups.com


On Sunday, October 19, 2014 8:26:14 PM UTC+1, Aslak Hellesøy wrote:


On Sun, Oct 19, 2014 at 7:34 PM, Christian <christia...@gmail.com> wrote:


On Sunday, October 19, 2014 11:51:25 AM UTC+1, Aslak Hellesøy wrote:
On Sun, Oct 19, 2014 at 11:38 AM, Christian <christia...@gmail.com> wrote:
Hi all,

When trying to extend a class that uses cucumber hooks, Cucumber throws the following error message:
cucumber.runtime.CucumberException: You're not allowed to extend classes that define Step Definitions or hooks

What's the rationale behind stopping us from doing that?


Cucumber creates a new instance of all classes defining stepdefs before each scenario.
It then invokes stepdef methods on *one* of those instances whenever it needs to run a step.

If you defined a stepdef method foo in class A and you have a class B extends A you'd get an a and b instance.
The foo method would be available on both instances, and Cucumber would not be able to decide what instance to invoke the method on.

My A is abstract and only holds Before and After hooks, not step defs, anyway, so there is little chance of invoking foo there. Plus, if you have a C extends B extends A chain, couldn't you just invoke the foo method where it is highest up the chain, i.e. in C?


What if your A has two direct descendants, B1 and B2. On what instance would you expect the hooks to be invoked?
I'd expect an exception telling me there are duplicate step definitions/hooks... :)
 
Why do you think you need inheritance in the first place?

In the past (before Cucumber), our RunTest files extended BaseTestFixture, which contains JUnit @Before hooks and processes annotations from RunTest, including some used for tagging.
Now we want to use the tags from the feature files instead, so a few things need to change. So we have a new class in the step defs folder, that just extends the BaseTestFixture and just holds the annotations that were previously part of the RunTest class. In theory, the class could have an empty body, but as we can't use cucumber hooks in the BaseTestFixture (because it gets extended) we have to add hooks into that new class whose sole purpose it is to call super.setup() and super.teardown(). And that for every new test set. Looks ugly, and is somewhat unnecessary...

Brgrds,
Christian

aslak hellesoy

unread,
Oct 21, 2014, 8:52:25 AM10/21/14
to Cucumber Users
Allowing inheritance in some cases and not in others just makes things complicated.

If you explain why you think inheritance is useful with a concrete example we can suggest some better options for you.

Aslak
 
> Brgrds,
> Christian
>
>>
>> That's why we don't allow it.
>>
>> The solution is to use composition instead of inheritance. You can achieve
>> composition with dependency injection - Cucumber supports several popular DI
>> frameworks.
>
> I'm trying to plug Cucumber into a project that has grown over many years.
> It's hard enough to get management's OK for that. If I now try to introduce
> something anywhere near as complex as Spring, for example, I'll most likely
> not get support for it. Is there a really lightweight DI framework you'd
> recommend?

PicoContainer is a very simple DI framework that requires no configuration.

Consider buying "The Cucumber for Java Book" (in beta at the moment)
that has answers to the most common questions:

https://pragprog.com/book/srjcuc/the-cucumber-for-java-book

This is an excerpt from the chapter on Dependency Injection:

http://media.pragprog.com/titles/srjcuc/simplify.pdf


Paolo

 Thanks, I'll have a look...

Brgrds,
Christian

--
Posting rules: http://cukes.info/posting-rules.html
---
You received this message because you are subscribed to the Google Groups "Cukes" group.

Roy Collings

unread,
Nov 3, 2016, 5:53:26 AM11/3/16
to Cukes
Hi Aslak,

I see this is an old thread, but I've got a concrete example for you ... a company I'm working with has 2 websites: one for UK and one for Germany. They're about 80% exactly the same (apart from language) and 20% unique.

This means we have a lot of scenarios and steps that are the same for both websites, and some more which are unique.

Aside from just copying the step definitions (and maintaining duplicates), what are our options here? Having a 'base' set of step defs we can inherit would be handy but, as you say, this leads to multiple definitions and cucumber wouldn't know which one to use. 

I've tried looking at dependency injection, but I can't see how that would help us in this situation. Any ideas?

Andrew Premdas

unread,
Nov 3, 2016, 10:14:28 AM11/3/16
to cu...@googlegroups.com
I'd so something really simple here which is have a second features directory common_features. How you get this into your two projects is upto you, perhaps a git submodule.?

Now all you have to do is modify your ci to run two sets of features rather than just one.

I think the discipline of having a unique world for each of the sites might actually help development, i.e ensuring that the scenarios in the features directory can only step definitions that are unique. So instead of having

Given I am a German user  # unique step
And I am logged in # shared step

you would have

Given I am logged in as a German user # unique step

You might want to share helper methods to avoid duplication (OK) and you could even share step definitions (messier) between the two sets of features.

I'd definitely never want to have a step do one thing in one context and something else in a different context, because errors will be harder to diagnose and fixes to step defs will have rippling consequences.

HTH

Andrew

To unsubscribe from this group and stop receiving emails from it, send an email to cukes+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
------------------------
Andrew Premdas

Roy Collings

unread,
Nov 7, 2016, 4:18:06 AM11/7/16
to Cukes
Thanks for the reply Andrew!

We'd considered that at the start but shied away because the generated reports are then split between 'common' features and 'specific' features ... which makes no sense to the business annalists and PM's for each project (and at the end of the day, they're a big 'customer' of these reports). People at that level shouldn't need to know that we've split tests up like that to make them easier to manage, it's irrelevant to them (our problem, not theirs), they just need the results.

We're also not in a position to influence the dev design on this.

Also, steps like "Then my displayed address matches my account address" etc... are steps that we do want to behave differently between sites (in GB and DE we have different fields for address, which doesn't matter for this business case, but does matter behind the scenes when automating). This isn't a problem for us as each test run only covers one site (it's not switching sights or anything, we just pass a parameter to decide which site when we kick it off).

I've only mentioned 2 countries there, but there are potentially dozens as the company expands so keeping that 80% commonality in a single place is going to be increasingly important.

All this said, I think our only option here is to split the features between 'common' and 'specific' and have an explanation at the start of the test reports to explain what that means.


------------------------
Andrew Premdas

Andrew Premdas

unread,
Nov 7, 2016, 9:47:56 AM11/7/16
to cu...@googlegroups.com
On 7 November 2016 at 09:18, Roy Collings <roy...@gmail.com> wrote:
Thanks for the reply Andrew!

We'd considered that at the start but shied away because the generated reports are then split between 'common' features and 'specific' features ... which makes no sense to the business annalists and PM's for each project (and at the end of the day, they're a big 'customer' of these reports). People at that level shouldn't need to know that we've split tests up like that to make them easier to manage, it's irrelevant to them (our problem, not theirs), they just need the results.

You can merge reports together if you really want to.
 
We're also not in a position to influence the dev design on this.

Also, steps like "Then my displayed address matches my account address" etc... are steps that we do want to behave differently between sites (in GB and DE we have different fields for address, which doesn't matter for this business case, but does matter behind the scenes when automating).

You can easily to this with environment variables. Have a language variable and make these steps use that to determine how to do address comparison.

Another way to do this would be to have the step call a helper method, but to include different helper methods in language specific files e.g.

Then 'my displayed address matches my account address' do
  addresses_match(display_address, account_address)
end

# support/german
module GermanStepHelper
  def addresses_match
     ...
   end
end

#support/english
module EnglishStepHelper
   def addresses_match
      ...
   end
end

Then include the particular step helper files using a profile e.g. cucumber -p profile

You can use this pattern to have a single set of features to do both languages, but you have to be very organised.
 
This isn't a problem for us as each test run only covers one site (it's not switching sights or anything, we just pass a parameter to decide which site when we kick it off).

I've only mentioned 2 countries there, but there are potentially dozens as the company expands so keeping that 80% commonality in a single place is going to be increasingly important.

All this said, I think our only option here is to split the features between 'common' and 'specific' and have an explanation at the start of the test reports to explain what that means.


These solutions demonstrate a couple of common patterns which are useful for most/all "I want cucumber to do foo" questions. You can

- go higher and make multiple calls to cucumber and then join the results
- or go lower and push down into pure language code. Once you are lower than cukes you can do whatever you want.

Really all cucumber should be about is translating natural language into programming calls. It doesn't need to do more, and if it does to much it becomes unwieldy and difficult to maintain.

All best

Andrew
To unsubscribe from this group and stop receiving emails from it, send an email to cukes+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Roy Collings

unread,
Nov 7, 2016, 10:04:03 AM11/7/16
to Cukes
"You can merge reports together if you really want to." ... not very easily. We're using Extent reports which are excellent and really easy to implement, but it would take a whole lot'o'learnin' to merge a few together.

The example of address was a little simplistic. Completing a salesflow, for example, becomes pretty complex between countries (for example, legal requirements push us down some different routes for different countries).

roberto....@ciss.com.br

unread,
Apr 27, 2017, 5:16:24 AM4/27/17
to Cukes
I'm facing this same problem. In all my Step Definitions I need to create the @After method and inside it take a screenshot if the scenario fails. The problem is that in all my current scenarios (230 scenarios) I need to add this, and in case I need any future changes I need to rewrite them all. What would be the best solution for this case since no inheritance is possible?

Paolo Ambrosio

unread,
Apr 27, 2017, 1:30:26 PM4/27/17
to cu...@googlegroups.com


On 27 Apr 2017 10:16 a.m., <roberto....@ciss.com.br> wrote:
I'm facing this same problem. In all my Step Definitions I need to create the @After method and inside it take a screenshot if the scenario fails. The problem is that in all my current scenarios (230 scenarios) I need to add this, and in case I need any future changes I need to rewrite them all. What would be the best solution for this case since no inheritance is possible?

Hopefully you don't have a different step definition for each feature!

Just declare the after hook once. Step definitons and hooks are global and NOT bound to a specific feature.

roberto....@ciss.com.br

unread,
Apr 27, 2017, 1:42:18 PM4/27/17
to Cukes
Due to the structure of my project I have a run cucumber for each step definitions, both in a folder, if I put a global hook, the cucumber is not able to see.
To unsubscribe from this group and stop receiving emails from it, send an email to cukes+un...@googlegroups.com.

Björn Rasmusson

unread,
Apr 27, 2017, 3:11:26 PM4/27/17
to Cukes
roberto....@ciss.com.br wrote:
Due to the structure of my project I have a run cucumber for each step definitions, both in a folder, if I put a global hook, the cucumber is not able to see.

Since Cucumber-JVM accept a set of packages with the --glue/@CucumberOption(glue={}) option, there is no problem to load the step definitions from one package and a global hook from another package.
 
Reply all
Reply to author
Forward
0 new messages