Phrases & Hiding Implementation Details
In the last section we looked at using Cucumber Expressions to extract DTO property keys from gherkin steps. This is a great way to keep your gherkin steps readable and reduce the number of step definitions you need to create.
One caveat is that for multi word properties, we need to write them in their in-code
convention - in this API that's camelCase
, meaning in our gherkin we reference our
discount percentage as discountPercentage
. This is fine but it exposes implementation detail
and doesn't read fluently.
Autometa has the concept of phrases
to map human friendly strings to their in-code counterparts.
This can be accomplished with the convertPhrase
function. This function takes a string and
returns a string. You can apply transformers to this string including prefixes, suffixes, trimming,
and changing the case.
In our case we want to convert discount percentage
to discountPercentage
. We can do this
by simply calling convertPhrase
using the camel
transformer.
import { convertPhrase, camel } from "@autometa/runner";
const result = convertPhrase("discount percentage", camel); // discountPercentage
Next we can combine this with the Cucumber Expression we built in the last section:
// src/product/product.params.ts
import { defineExpression, AssertKey } from "@autometa/runner";
import { ProductBuilder } from "./";
defineExpression({
name: "product:property",
transform: (value) => {
const property = convertPhrase(value, camel);
AssertKey(ProductBuilder, property, `Product property key ${property}`);
return value;
}
});
Nothing changes in how we consume this expression in step definitions.
Suffixes
It's likely that properties on the world follow some pattern. For example,
maybe all API responses are stored in the format {foo}Response
. We can
define this in our phrases to easily extract responses from the world.
We can add this as a suffix using the sfx
transformer:
// src/product/product.params.ts
import { defineExpression, AssertKey, sfx, camel } from "@autometa/runner";
defineExpression({
name: "world:property:response",
regex: [/"(.*)"/, /'(.*)'/]
transform: (value) => {
const property = convertPhrase(value, sfx`Response`, camel);
return value;
}
});
Note: we have no way of asserting properties on the world at this point.
And update our types:
import type { App as MyApp, World as MyWorld } from "../src/app";
import type { ProductBuilder, Product } from "../src/product";
declare module "@autometa/runner" {
interface App extends MyApp {}
interface World extends MyWorld {}
interface Types {
"builder:product": ProductBuilder;
"product:property": keyof Product;
"world:property:response": keyof World;
}
}
Which can use in our step definitions:
// integration/steps/product/given.steps.ts
import { Given, When, Then } from "@cucumber/cucumber";
import { World } from "../world";
Given(
"the product {product:property} is {world:property:response}",
function (property, response, { world }) {
const { data } = world[response] as HTTPResponse;
this.builder[property] = data[property];
}
);
Finally, before we start writing our gherkin, we want to think about static data.