Storing Data Between Tests
An issue arises with steps when data must be passed between them. In vanilla Cucumber, that is achieved with the World object.
As Jest-Automation is a structured test runner, it is possible to create objects inside the test Scenarios themselves.
Feature(({ Scenario }) => {
Scenario('Search Google', ({ Given, When, Then }) => {
const data: any = {}
Given('a user on the google homepage', ()=>{
data.user = new User()
})
When('they search for {string} on google', async (searchTerm)=>{
data.result = await data.user.SearchGoogle(searchTerm)
})
Then('they see a result for {string}', (website)=>{
const desiredWebsite = getWebsite(website, data.result);
expect(desireWebsite).toBeDefined()
// other validations
})
});
}, './sample.feature);
This works, but there is no type safety and no way to communicate this object with shared steps.
const setupUser: Steps = ({ Given }) => {
Given('a user on the google homepage', ()=>{
data.user = new User() // uh-oh
})
});
To work around it, shared steps can be wrapped in a function which accepts the data object.
const setupUser: Steps = (data: any) => {
return ({ Given }) => {
Given('a user on the google homepage', () => {
data.user = new User(); // ok
});
};
};
To reduce on boiler plate, you have access to...
The World Object
The World object behaves similarly to vanilla cucumber. It is an empty object which accepts any value and returns unknown when accessed.
The World object is available in the second argument of the scenario
call back and can be destructured with the name World
Feature(({ Scenario }) => {
Scenario('Search Google', ({ Given, When, Then }, { World }) => {
Given('a user on the google homepage', ()=>{
World.user = new User()
})
When('they search for {string} on google', async (searchTerm)=>{
World.result = await data.user.SearchGoogle(searchTerm)
})
Then('they see a result for {string}', (website)=>{
const desiredWebsite = getWebsite(website, World.result);
expect(desireWebsite).toBeDefined()
// other validations
})
});
}, './sample.feature);
World is unique to each Scenario (including the scenarios of a Scenario Outline, and scenarios automatically constructed by Automatic Scenarios).
World is automatically injected into all Shared Steps.
const setupUser: Steps = ({ Given }, { World }) => {
Given('a user on the google homepage', ()=>{
World.user = new User();
})
});
Store
Alternatively, like World, the second argument contains a unique Store object, which is also injected into Shared Steps.
Store caches values with its put method and they can be retrieved with read.
put takes a key, which is a string, and a value to store.
Scenario('a scenario', ({ When }, { Store }) => {
// ... given code
When('the the user POSTs their details', async () => {
const response = await ServiceClient.post({ username: 'freddy' });
Store.put('returnedResponse', response);
});
});
read also takes a key and returns the value associated with it, if any, which can be cast to a type argument.
Scenario('a scenario', ({ Given }, { Store }) => {
// ... given, when code
Then('the user object is returned', () => {
const expected = { username: 'freddy' };
const response = Store.read<UserResponse>('providedNumber', numVal);
expect(response).toBe(expected);
});
});
Optionally, both methods accept validation options which if configured
will cause the Store instance to log a warning and/or throw an error
when a null or undefined value is passed to the store.
interface ValidationOptions {
warn?: boolean;
throws?: boolean;
}
When warn is set to true, and a value of null or undefined is added or accessed, it will cause a console.warn to be issued. If a read fails and warn is enabled, a report will be printing indicating if a value has ever been added for that key, or it was never putted at all.
throws throws an error when a null or undefined value is found.
Store.put('returnedResponse', response, { warn: true });
....
const storedResponse = Store.read<UserObject>('returnedResponse', {
error: true,
});