Cucumberjs - sharing state between steps

When authoring tests in gherkin and using Cucumberjs for the step implementation, you will end up with a scenario in which you need to share data between the steps. The most obvious solution to this is to bound the data to the this, in cucumberjs terms it’s called World. The actual implementation could look like this:

Given("my color is {string}", function(color) {
  this.color = color
})

And it just works, but it has two disadvantages. First, as your scenarios grow in size and you have a fairly large collection of step implementations, it is very difficult to track what property is available at what step. Second, you might unintentionally override already existing property. Unfortunately, this is impossible to solve out of the runtime execution easily. But to mitigate the issue on the runtime level I came up with a few lines of code, that help to track the current state of shared test data. In case you use screenplay(-like) pattern for your tests, you might use it in the “remember” and “recall” abilities, or create a shared context management class and instantiate it in a new custom world.

First off, we need to create the class and its memory:

export class TestDataMemory {
    private memory

    constructor(){
        this.memory = new Map()
    }
}

And now we can proceed with the “save” method implementation:

    save<T>(key: string, value: T): void {
        if (this.memory.has(key)) {
            throw new Error(
                `You tried to override "${key}" property. This is not allowed`)
        }
        this.memory.set(key, value)
    }

Here, I addressed one of the above-mentioned disadvantages - not being allow to override a property in case it had already been saved. Of course, this could be further adjusted to allow property overriding if needed. But in our case, we never needed it, so let’s continue without it. Now, let’s have a look into how we can “load” the data from the memory.

    load<T>(key: string): T {
        if (this.memory.has(key)) {
            return this.memory.get(key)
        }
        throw new Error(`You tried to access ${key} property, but it does not exist.`)
    }

Thus, during test execution, if you try to access a property that was not saved yet, the method will throw an error with a descriptive message. This approach helps me to reduce the number of guesses and blind spots in the shared test data context.

Published 19 May 2022