Skip to main content

App & World

caution

This library uses TSyringe to support dependency injection. You will need a reflect pollyfill like reflect-metadata to use the App functionality. reflect-metadata must be imported early in your project and ideally only once. A good place to import it is the very first line of autometa.config.ts

autometa.config.ts
import 'reflect-metadata'
...

The App fixture is undefined by default. It must be created by you in your project, and configured with defineConfig. The App must be class. It must be decorated with Fixture and Persistent decorators.

Once configured, the app will be instantiated with a new context for every scenario run. It contains user defined data and will be passed to every step callback when executed. It will always be the last argument passed to the callback. If there are no variables, docstrings or data tables it will be the only argument.

src/my-app.ts
import { Fixture, Persistent } from "@autometa/cucumber-runner";

@Fixture
@Persistent
export class MyApp {}

The app must be defined in config:

autometa.config.ts
import { defineConfig } from "@autometa/cucumber-runner";
import { MyApp } from
defineConfig({
app: MyApp
...
})

Now the app will be available in all step definition callbacks:

globals/given.steps.ts
import { Given } from "@autometa/cucumber-runner";
import { MyApp } from "../src";

Given("a step", (app: MyApp) => {
expect(app).toBeInstanceOf(MyApp); // pass
});

World

The App has been defined but it's seemingly useless. It would be nice to store data between our steps if there is state that must be shared. We could do that on the App itself, but Cucumber already has a concept for storing arbitrary data: the implicit World.

In Cucumber, step definition callbacks are bound to the World. To access it, then, one must use the this keyword, which necessitates the use of function syntax functions, as (fat) => arrow functions cannot access the world.

In Autometa, the World is explicit. To create your own world, define a class with an index signature so that properties can be arbtirarily defined and read.

export class World {
[key: string]: unknown;
}

This makes all properties of World unknown, forcing proper type handling in tests.

However it would be nice to see what properties we expect tests to want to touch. We can do that using the declare keyword:

import { Fixture, Persistent } from "@autometa/cucumber-runner";
@Fixture
@Persistent
export class World {
[key: string]: unknown;

declare myRequest: Request<MyDto>;
declare userCount: number;
}

This lets tests know that these properties exist and what they look like. At this point the properties do not exist, but they can be written to in Given and When steps and accessed from Then steps (for example).

tip

@Persistent lets Autometa know that the same copy of this object should always be passed to all dependents per scenario, rather than create a new instance for each consumer.

As World is intended to store test-persistent data, it should be Persistent.

Attaching World to App

Finally, we want to let our App know about World so it becomes available in tests.

Simply define it as a public (or readonly( constructor parameter on App.

src/my-app.ts
import { Fixture, Persistent } from "@autometa/cucumber-runner";

@Fixture
@Persistent
export class MyApp {
constructor(readonly world: World) {}
}

And that's it. MyApp and World are now available in tests.

Given("a setup step", ({ world }: App) => {
world.myRequest = {
/* request json */
};
});

When("an action step", async ({ world }: App) => {
world.userCount = await fetch({
body: world.myRequest
});
});

Then("a validation step", ({ world }: App) => {
expect(world.userCount).toEqual(3);
});