amiceli / vitest-cucumber

Use gherkin in your unit tests with vitest
https://vitest-cucumber.miceli.click
41 stars 5 forks source link

Proposition: global keyword functions #142

Closed Odonno closed 1 month ago

Odonno commented 1 month ago

This library follows the pattern implemented in jest-cucumber where keyword functions like Scenario, Given, When, etc... are imported from the parameters of the parent function.

cucumber-js chosed a different approach and provide these functions directly from the package itself. See the example provided by the documentation of the project:

const assert = require('assert')
const { When, Then } = require('@cucumber/cucumber')
const { Greeter } = require('../../src')

When('the greeter says hello', function () {
  this.whatIHeard = new Greeter().sayHello()
});

Then('I should have heard {string}', function (expectedResponse) {
  assert.equal(this.whatIHeard, expectedResponse)
});

Here it is using a require import but the library supports the import pattern as seen in this project example: https://github.com/cucumber/cucumber-js-examples/blob/main/examples/typescript-node-esm/features/step_definitions/steps.ts

The proposition is to follow this implementation to simplify the developer experience given the following benefits:

amiceli commented 1 month ago

@cucumber/cucumber is often taken as an example but I don't follow same way.

I create vitest-cucumber to joins feature file and unit tests, it's the most important thing.

It can annoying but it's an opinionated project, use When, Given etc without Scenario, Rule it's useless. I won't include this feature.

For RuleScenario good point, I can use same Scenario function name. I will check it.

Odonno commented 1 month ago

Ah, yes, I see your confusion now reading my proposition.

I like the taken approach to NOT have parentless Given, When, etc... I find it more readable and also more grounded (to avoid steps without scenario even though there are tools to prevent that).

Basically, the idea was to import those keywords from an import instead of it being injected from parameter. See the expected output:

import { loadFeature, describeFeature, Scenario, Given, When, Then, And } from '@amiceli/vitest-cucumber'

const feature = await loadFeature('./example.feature')

describeFeature(feature, () => {
    Scenario('Run unit tests', () => {
        Given('I have installed vitest-cucumber', () => {})
        And('I have a feature like "example.feature"', () => {})
        When('I run vitest-cucumber', () => {})
        Then('My feature file is parsed', () => {})
        And('I can test my scenarios', () => {})
    })
})

In this example it does not to bring value but when you tens of scenarii, you will have a boring & useless repetitive pattern. But I think you got the point.

amiceli commented 1 month ago

Ok, I understand better your proposition. I prefer to keep Given, When etc as parameter.

Globally it will change lot of code to detect "parent", detect When forbidden in Background etc.

To be less boring, I use one parameter in callback :

describeFeature(feature, (f) => {
    f.Scenario(`DocStrings example`, (s) => {
        s.Given(`I use vitest-cucumber`, () => {})
        s.Then(`I use vitest`, () => {})
    })
})
Odonno commented 1 month ago

Ok then.