-
Notifications
You must be signed in to change notification settings - Fork 118
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
Need I really to define each Scenario with its dependent steps outside of the feature file ? #52
Comments
You can check this link |
And when i want to reuse the steps in other features ? Then i have to outsource them anyway... and their implementation should at least be in one place so i used this approuch at the beginning but it didnt solve the Problem. Thanks anyway. For clarity: I use jest-cucumber in combination with puppeteer so I have to reuse a lot of the Steps all over the different features. |
I didn't quite understand what you meant here, so pardon me if this is what you already tried. If you define your step definitions inside of functions, you can place them in shared modules and import them in multiple places: // shared-steps.js
export const givenIHaveXDollarsInMyBankAccount = given => {
given(/I have \$(\d+) in my bank account/, balance => {
myAccount.deposit(balance);
});
};
export const thenMyBalanceShouldBe = then => {
then(/my balance should be \$(\d+)/, balance => {
expect(myAccount.balance).toBe(balance);
});
}; // example.steps.js
import { thenMyBalanceShouldBe, givenIHaveXDollarsInMyBankAccount } from './shared-steps';
defineFeature(feature, test => {
let myAccount;
beforeEach(() => {
myAccount = new BankAccount();
});
test('Making a deposit', ({ given, when, then }) => {
givenIHaveXDollarsInMyBankAccount(given);
when(/I deposit \$(\d+)/, deposit => {
myAccount.deposit(deposit);
});
thenMyBalanceShouldBe(then);
});
test('Making a withdrawal', ({ given, when, then }) => {
givenIHaveXDollarsInMyBankAccount(given);
when(/I withdraw \$(\d+)/, withdrawal => {
myAccount.withdraw(withdrawal);
});
thenMyBalanceShouldBe(then);
});
}); When you import shared functions like this, you will still need to invoke them in every step definition file, which you might consider to be duplicate code. The philosophy behind that is that it's more readable to see the steps shown in the test code in the same order they appear in the feature file, but not everyone may agree.
That's right. |
@pedrowilkens, your "hack" is actually an interesting idea. It should be possible to use something like that to build on top of the existing API and provide an option that is more similar to Cucumber for people who prefer that. As an example of what could be possible, a file like this could get registered with Jest and would create the Jest tests automatically: // scenarios.spec.js
import { wireStepsToFeatures } from 'jest-cucumber';
wireStepsToFeatures('../../features/**/*.feature', '../step-definitions/**/*.js'); This would be an example of how step definitions are defined: export default ({ given, when, then }) => {
let accountBalance = 0;
given(/I have \$(\d+) in my account/, (amount) => {
accountBalance += amount;
});
when(/I deposit \$(\d+)/, (amount) => {
accountBalance += amount;
});
then(/my balance should be \$(\d+)/, (balance) => {
expect(accountBalance).toBe(balance);
});
}; It would have to grab all of the step definition files from the glob pattern and evaluate them with The downside would be that the vscode-jest extension wouldn't work, and the tests wouldn't be as readable (in my opinion). It seems there are enough people who expect jest-cucumber to work like this that it might be worth doing, though. |
Yes, that would be interesting to do. It would have the downside that you have only 1 "test/spec" file and the features would be all in one test suite so the regexp filter system of jest would not be applicable to all separated features. I don't know which way it could be implemented in Typescript since I'm learning Typescript right now. Defined Steps: // general.steps.js
import Steps from './lib/Steps'
Steps.add(/^I enter "([^"]*)"' into the input field$/, async (text) => {
await page.type('.inputfield', text)
})
export { Steps } If you want to seperate the steps in different files. // index.steps.js
import { Steps } from './general.steps'
import { Steps as specialSteps } from './special.steps'
import { Steps as otherSteps } from './other.steps'
Steps.concat(...specialSteps, ...otherSteps)
export { Steps } Steps class which adds the step functions with its corresponding regexp // Steps.js
class Steps {
constructor() {
this.list = []
}
// add steps and check if they are defined already
add(regExp, func) {
const isAlreadyAdded = this.list.find((step) => String(step.regExp) === String(regExp))
if (isAlreadyAdded) {
throw Error(String(regExp) + ' : Step is already added. Please remove duplicate.')
}
this.list.push({regExp, func})
}
// If there are more step files they can be concatenated
concat(...lists){
this.list = this.list.concat(...lists)
}
// checks if the corresponding step function is defined and returns it
get(title){
let params
const found = this.list.find((step) => {
const matches = title.match(step.regExp)
if (matches) {
params = matches.slice(1)
return true
}
return false
})
if (!found) {
throw Error(title + ' : was not defined in Steps file')
}
return found.func.bind(this, ...params)
}
}
export default new Steps() The function which generates the tests // wireStepsToFeatures.js
import { defineFeature, loadFeature } from 'jest-cucumber'
export function wireStepsToFeatures(featureFile, Steps) {
const feature = loadFeature(featureFile, tagFilter)
defineFeature(feature, test => {
feature.scenarios.forEach((scenario) => {
test(scenario.title, (stepName) => {
scenario.steps.forEach((step) => {
stepName[step.keyword](step.stepText, Steps.get(step.stepText))
})
})
})
}) The test file which wires them up. // file.test.js
import { Steps } from './index.steps'
import { wireStepsToFeature } from './wireStepsToFeature.js'
wireStepsToFeature(featureFile, Steps) And the way someone would use it if it would be available through jest-cucumber // general.steps.js
import { Steps } from 'jest-cucumber'
Steps.add(/^I enter "([^"]*)"' into the input field$/, async (text) => {
await page.type('.inputfield', text)
})
export { Steps } // file.test.js
import { Steps } from './general.steps'
import { wireStepsToFeature } from 'jest-cucumber'
wireStepsToFeature('tests/features/some.feature', Steps) I know that's not optimal but maybe there is a better way doing it in a more defensive way also I don't know how it behaves since you parse the featureFile and the scenarios in the feature file will be skipped duo tag filter? Also, it could just be another package like jest-cucumber-wired-up so it won't be confused with the normal way and it would force the package-user to think about the need of using it or not. I also think using it or not depends on how many steps need to be outsourced. If you heavily outsource the steps the benefit of defining the scenarios explicitly with references of actual steps is low because the implementation of the steps is not at the same place as the Scenario definitions (in the test/spec file). In my case, I needed to do this. Because I have 10 comprehensive feature files. And I reuse a lot. |
Nice, thanks for sharing! I think that
Tag filters should be fine. The only thing is that you're hiding
Yes, I definitely agree that the jest-cucumber syntax is less useful when there are a lot of duplicated steps in the feature files. The current syntax is designed with the assumption that there aren't a lot of duplicated steps. I often find that too many duplicated steps in a feature file is a "code smell" that suggests the language in the feature files is too low-level. In my experience, describing high-level business logic in feature files (e.g., given I have 2 products in my cart) instead of lower-level UI interactions (i.e., click this, then click that, etc.) results in a ton less duplicate steps. Each step ends up with specific and unique usage of domain language instead of repeating knowledge that already exists in other steps. In my projects, I tend to end up with a few steps being duplicated within the same feature files, but rarely duplicated across feature files. One reason is that cross-cutting concerns (e.g., logging in) are typically described in one feature file and then implied everywhere else. For example, if you're looking at your account details in an app, then you're obviously logged in. Using "screen" and "workflow" abstractions described in books like "The Secret Cucumber Ninja Scrolls" greatly reduces duplication in the implementation code as well.
Agreed, a separate library would keep things lean and avoid confusion. It shouldn't run into problems, since it calls the public API and doesn't really need access to the internals. Maybe the docs about avoiding duplicate steps could include a link to the new library. |
Hi, are there any updates here? |
I think it's worth implementing in jest-cucumber directly, since this is such a common request. This will be next in line after #27 is completed. |
Any word on this stuff? As it sits, (unless I am missing something huge) it seems like the way steps have to be defined (contextually tied to a specific scenario), it seems like jest-cucumber defeats some of the purpose of cucumber. (bdd testing seems to be particularly well suited for allowing non software engineers to use prebuilt steps to build tests) |
Hi @bencompton @pedrowilkens did you advance any on this work? |
As im working with jest-cucumber for some time, i was asking myself if it is really usefull if i need to allways define the scenarios and its related steps in defineFeature callback because that leads to duplicated code.
If i want to avoid this i need a hack like this:
Is my understanding correct ? Because every Scenario must be defined with its steps, i need to define them more then once.. ?
If there is another way and i am wrong, i would be very happy to know about it
Btw im reading your source code, because im working on cucumbers "Background" problem. Since its not implemented yet, everyone just uses beforeEach instead right?!
The text was updated successfully, but these errors were encountered: