App & World
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
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.
import { Fixture, Persistent } from "@autometa/cucumber-runner";
@Fixture
@Persistent
export class MyApp {}
The app must be defined in config:
import { defineConfig } from "@autometa/cucumber-runner";
import { MyApp } from
defineConfig({
app: MyApp
...
})
Now the app will be available in all step definition callbacks:
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).
@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.
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);
});