Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling steps from step definitions #11

Closed
jbpros opened this issue Jun 1, 2011 · 31 comments
Closed

Calling steps from step definitions #11

jbpros opened this issue Jun 1, 2011 · 31 comments

Comments

@jbpros
Copy link
Member

jbpros commented Jun 1, 2011

Allow calling steps from step definitions.

https://github.com/cucumber/cucumber/wiki/Calling-Steps-from-Step-Definitions

@mattwynne
Copy link
Member

When this is implemented, bear in mind that we plan to deprecate everything other than #steps for doing this in cucumber-rb. See cucumber/common#68

@jbpros
Copy link
Member Author

jbpros commented Jun 1, 2011

Thank you Matt. -js will only support steps() then.

I like it!

@dbrock
Copy link

dbrock commented Jan 8, 2012

Any progress on this? Seems like a pretty vital feature.

@jbpros
Copy link
Member Author

jbpros commented Jan 8, 2012

This is not planned as part of the current milestone (0.3). It should be part of 0.4.

@jbpros
Copy link
Member Author

jbpros commented Mar 14, 2012

@mattwynne I guess we want to support step() as well. Am I correct?

@mattwynne
Copy link
Member

@jbpros I guess. Maybe you could start with #step. It is simpler to implement because you're just invoking a step rather than parsing Gherkin.

I personally would be happy to use a Cucumber without this feature, I never use it and consider it to be bad practice. I always prefer to delegate to methods instead.

Ultimately, if we're going to support this, I would prefer to see it implemented in Gherkin, so you have a way of defining a macro step that maps to a bunch of other low-level steps. Cucumber then just gets told to invoke the lower level ones and hardly needs to know there's any mapping going on. That would be my preference.

@jbpros
Copy link
Member Author

jbpros commented Mar 15, 2012

TL;DR: Should we really add steps()/step() to Cucumber.js (and -jvm, -ruby 2, etc.)?

I totally agree with you Matt. Unfortunately, this is the most wanted feature right now on Cucumber.js.

As I understand it, many people consider step definitions as methods or functions. The way I see it, they're mappings between some fluent language sentences and pieces of programming language code, nothing more. This has deep implications as to how we treat those beasts.

From a programmer perspective, step definitions look just like methods though. I see it as a weakness in Cucumber_s_ today. Step definitions should not be exposed as an API but rather as an explicit translation map, a dictionary, so to speak.

@msassak had interesting thoughts on this already and I think he did a great job re-modeling those "mappings" in Cucumber 2.0.

I have to say I'm reluctant to work on this very issue in Cucumber.js right now. On the other hand I don't want to do feature retention just because of my personal tastes/opinions.

@aslakhellesoy
Copy link
Contributor

👎 👎 👎 👎 👎

Calling steps from stepdefs is one of those features I wish I never added to Cucumber(-Ruby), because it provides so much rope for people to hang themselves with. It came to be because Ruby stepdefs use anonymous closures that you can't call from elsewhere (unless you go through hoops).

With JavaScript it's a different situation; Step Definitions use first class functions!

function x_is_something(x, cb) {
  console.log('x', x);
  cb();
}

Given(/x is (.*)/, x_is_something);

Given(/super x/, function(cb) {
  x_is_something(97, cb);
});

@jbpros
Copy link
Member Author

jbpros commented Oct 16, 2012

CLOSED (WONTFIX) 🔨

@jbpros jbpros closed this as completed Oct 16, 2012
@jbpros
Copy link
Member Author

jbpros commented Mar 27, 2014

I'm not sure I understand how step() would be better than a simple JS function call here. Isn't that what it'd end up doing anyway, with an additional layer of indirection (i.e. a step definition to make a GET request to a specific user that still needs to be translated into a JS function)?

I noticed you wrote since I have no way to trigger a scenario, did you mean step or is that intentionally scenario (in the latter case, I can see what you're trying to do, I think).

You can still define users in a background and iterate over them in your When step.

Feature:
  Background:
    Given a valid user called Simon
    And a valid user called Sarah
    And a valid user called Johnny

  Scenario Outline:
    When each valid user sends a GET request to /search<query>
    Then everyone's request response code is 400
    And everyone's request response body starts with <body>

  Examples:
    | query  | body |
    | ?foo=1 | ...  |

And yes this means storing request responses in an array and iterating over them. Is it that bad?

@cono
Copy link

cono commented Aug 13, 2014

Well, if you have some flow (for example checkout flow) and you want to get to the final confirmation page, you can go through steps, defined (somewhere) in the step definitions.
I agree that you can also define a function somewhere and invoke it from the step definition. But its minimize the effort of moving it from steps to function. If you described some flow somewhere in BDD, you don't need to spend additional time for moving it to separate library, you can simply invoke step definitions.

Invoking one single step is almost useless. I was thinking of porting all ruby's step(s) functionality here. But as my request closed, I would not spend time for it.

Thanks.

@amatiasq
Copy link

So sad this won't fix, as @cono says: invoking one single step is almost useles, real cases are more complex operations.

This would be really helpful to create a few fine-grained scenarios and then the others scenarios repeating this operations. Specially when the steps are defined in more than one file, in that case function-reuse alternative is not quite easy nor clean.

@hackhat
Copy link

hackhat commented Feb 26, 2015

Hy! I've created a lib which does exactly what you are asking (call a step from another step), take a look here: https://github.com/hackhat/cucumberry
Feedback is welcome!

@jlin412
Copy link

jlin412 commented Feb 26, 2015

@hackhat It looks really cool. I usually like sync portion of step definition. Is cucumber-pro only a plugin for cucumber.js?

@hackhat
Copy link

hackhat commented Feb 26, 2015

@jlin412 I don't really know how to call, but is like a helper for cucumber. Thanks for feedback

@jlin412
Copy link

jlin412 commented Feb 26, 2015

@hackhat In order to create sync step, I will need to use syntax: this.addStep(...)? Will I need to use selenium-sync as well instead of protractor.js/webdriver.js?

@hackhat
Copy link

hackhat commented Feb 26, 2015

@jlin412 you can only use the sync plug-in, look at the docs how to do it. I'm on mobile so I can't give you the exact steps. If you can wait I will explain better around 23h30 Portugal hour.

@aslakhellesoy
Copy link
Contributor

@hackhat please change the name of your project to something else. See hackhat/cucumberry#1

@hackhat
Copy link

hackhat commented Feb 27, 2015

@aslakhellesoy Changed the name to https://github.com/hackhat/cucumberry

@yunjia
Copy link

yunjia commented May 13, 2015

@aslakhellesoy Then how to chain the step calls? Like following?

function x_is_something(x, cb) {
  console.log('x', x);
  this.x = x;
  cb();
}
function y_is_something(y, cb) {
  console.log('y', y);
  this.y = y;
  cb();
}

Given(/super z/, function(cb) {
  x_is_something(97, cb);
  y_is_something(8, cb);
});

That doesn't work very well since x_is_something would have called the callback before y_is_something has chance to finish its work.

And also, if step stores variable in the world context, it will end up each time call the function, we need to bind it like:

Given(/super z/, function(cb) {
  x_is_something.bind(this)(97, cb);
  y_is_something.bind(this)(8, cb);
});

Anyone had solutions to these issues?

@hackhat
Copy link

hackhat commented May 13, 2015

You need to use async and use the parallel function. In this way you call
the main cb only after both of the sub calls have finished.
About binding you can either use this with bind or use a closure variable.

On Thu, May 14, 2015, 00:15 Yun Jia notifications@github.com wrote:

@aslakhellesoy https://github.com/aslakhellesoy Then how to chain the
step calls? Like following?

function x_is_something(x, cb) {
console.log('x', x);
this.x = x;
cb();
}function y_is_something(y, cb) {
console.log('y', y);
this.y = y;
cb();
}

Given(/super z/, function(cb) {
x_is_something(97, cb);
y_is_something(8, cb);
});

That doesn't work very well since x_is_something would have called the
callback before y_is_something has chance to finish its work.

And also, if step stores variable in the world context, it will end up
each time call the function, we need to bind it like:

Given(/super z/, function(cb) {
x_is_something.bind(this)(97, cb);
y_is_something.bind(this)(8, cb);
});

Anyone had solutions to these issues?


Reply to this email directly or view it on GitHub
#11 (comment)
.

@ghost
Copy link

ghost commented Jun 12, 2015

+1, this should be part of the lib.

What @mattwynne suggested (add a gherkin feature which supports code reusability):

---------------------------------

    When a
    Then x
    When b
    Then y

---------------------------------

    Define x
        Then p
        And y
        And q

---------------------------------

    Step a
        ...
    Step b
        ...
    Step p
        ...
    Step q
        ...
    Step y
        ...

---------------------------------

What @aslakhellesoy suggested (extracting the duplicated code to js functions):

---------------------------------

    When a
    Then x
    When b
    Then y

---------------------------------

    Step a
        ...
    Step b
        ...
    Step x
        ...
        Call f(p)
        ...
    Step y
        Call f(p)

---------------------------------

    Function f(p)
        ...

---------------------------------

Calling steps from step definitions

---------------------------------

    When a
    Then x
    When b
    Then y

---------------------------------

    Step a
        ...
    Step b
        ...
    Step x
        ...
        Call y(p)
        ...
    Step y
        ...

---------------------------------

I still don't understand why do we need another unnecessary abstraction level, do you have any explanation?

@jbpros
Copy link
Member Author

jbpros commented Jun 12, 2015

@yunjia this is basic callback theory:

Given(/super z/, function(cb) {
  x_is_something(97, function () {
    y_is_something(8, cb);
  });
});

As for the binding issue, you should define those functions as methods on your World:

function World(callback) {
    this.x_is_something = function (x, callback) {
      this.x = ...
    };

    this.y_is_something = function (y, callback) {
      this.y = ...
    };

    callback(); // tell Cucumber we're finished and to use 'this' as the world instance
  };
}
module.exports.World = World;

Then in your step definitions:

Given(/super z/, function(cb) {
  var self = this;
  self.x_is_something(97, function () {
    self.y_is_something(8, cb);
  });
});

Also, please note that synchronous step definitions are supported by Cucumber.js 0.5+:

Given(/super z/, function() {
  this.x_is_something(97);
  this.y_is_something(8);
});

@jbpros
Copy link
Member Author

jbpros commented Jun 12, 2015

@Inf3rno step definitions are a thin translation layer between plain english and JS code. By allowing calling steps from steps, we make that layer fatter. Steps become coupled between each other, rendering them extremely difficult to maintain.

It also encourages people to use Gherkin as a scripting language, which it is not at all.

@aslakhellesoy
Copy link
Contributor

@Inf3rno if you want to reuse code in stepdefs, move the body of the stepdef to a regular javascript function and reuse that.

@ghost
Copy link

ghost commented Jun 12, 2015

@jbpros

It also encourages people to use Gherkin as a scripting language, which it is not at all.

Can you elaborate pls. I don't understand what is the connection, since step definitions are not in gherkin, only the text pattern what is similar.

@jbpros
Copy link
Member Author

jbpros commented Jun 12, 2015

@Inf3rno if you can call steps from steps, you're jumping back again into "Gherkinland": the step names need to be parsed, matched against step definitions which can be executed. You're basically writing gherkin scripts within gherkin (they're hidden within JS step definitions, but that's a detail that makes it even worse from a maintenance POV).

@AutoSponge
Copy link

@aslakhellesoy @jbpros The idea is that steps should be an algebraic, composable type, which it's not.

@JasonSmiley208
Copy link

@jbpros, I am going to use your solution since I am used to programming in Java and not worrying about promises :)

@stevecooperorg
Copy link

Did anyone ever come up with an extension to cucumber for defining steps in terms of other steps? Something like

Understand I log in to {site} as {user}, {pass}
    I visit {site}
    I log in as {user}, {pass}

Am pondering whether this is a useful extension to better describe long user journeys through the system and would prefer to work with any prior art.

@lock
Copy link

lock bot commented Oct 25, 2018

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Oct 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests