Opinionated Cucumber
Goals
Tests should be:
- easy to read & validate
- predictable
- quick to write
Solution
- Instead of hundreds (potentially thousands) of unique but repetitive steps, have a few dozens of fully reusable steps.
- The truth should not be obscured inside step implementations. Expose it by moving the truth from .js into .feature files.
Before
.then('I should see $count $type nodes?', async function(count, type) {
.then('I should see $number trees', function(count) {
.then('I should see the table for the selected assumption', async function() {
.then('I should see the commit dialog', async function() {
.then('I should see the $type message "$message"', async function(messageType, messageText) {
.then('I should see $number trees', function(count) {
.then('I should see $count assumption group(?:s|) for the $index tree', async function(count, index) {
.then('I should see $count assumption(?:s|) for the $index group of the $index tree', async function(count, index, treeIndex) {
.then('I should see a list with $count scenario-sets', function(count) {
.then('I should see the scenario-set "$name"', async function(name) {
.then('I should see an? $type element', async function(type) {
.then('I should see the info-message "$message"', function(message) {
.then('I should see $count form error(?:|s)', async function(count) {
After
.then('I should see $element', async function(element) {
.then('I should see $count $element', async function(count, element) {
.then('I $element should have text', async function(element, text) {
data-test-label="<Save>"
data-test-label="<Menu Item><About Us><Is Active>"
find('[data-test-label*="<About Us>"]')
find('[data-test-label*="<Menu Item>"][data-test-label*="<Is Active>"]')
findByLabel('<Menu Item><Is Active>')
When I type "Foo" into the <Name><Field>
When I type "Foo" into the <Name><Field> of the <Assumption Form>
When I click the 2nd <Reaction Emoji> of the 4th <Comment> inside the 1st <Tread> of the 2nd <Post>
When I type "Foo" into the 2nd <Field>
the 2nd <Field> of the 4th <Field Group>
inside the <Assumption Form>
[data-test-label*="<Assumption Form"]
[data-test-label*="<Field Group>"]:eq(4)
[data-test-label*="<Field>"]:eq(2)
.then("$element should (not )?have HTML class $text",
function (element, stateRaw, text) {
const state = stateRaw !== "not ";
// chai
expect.equal(element.classList.includes(text), state);
// chai-dom
state
? expect(element).to.have.class(text)
: expect(element).not.to.have.class(text)
})
Compact library of
100% reusable steps
- When I click <Element>
- When I fill field <Element> with text "These are
not the droids you're looking for" - When I clear field <Element>
- When I enable checkbox <Element>
- When I move mouse pointer into <Element>
- When I move mouse pointer out of <Element>
- When I select "Yoda" in dropdown <Element>
Compact library of
100% reusable steps
- Then I should see <Element>
- Then I should see 2 <Element>s
- Then <Element> should have text "Use the force, Luke!"
- Then <Element> should have HTML class "is-active"
- Then <Element> should have HTML attr "disabled"
- Then <Element> should have HTML attr "aria-label"
with value "button" - Then dropdown <Element> should have "Lightsaber" selected
- Then table <Element> should be sorted by column "Rank"
in order "asc"
Seeding
We have scenarios with:
-
Given there is 1 scenario-set with assumptions in my database
-
Then I should see 5 assumptions for the first group of the first tree
-
Then the first assumption should have name "Foo"
A universal approach
Given there is 1 assumption-group in my database with the following properties:
---
scenarioSetId, 1
assumptionTreeId, 1
---
Much better, but has issues:
- too technical
- too lengthy
Given a scenario set with "Id": "@scen1", "Name": "Foo"
And an assumption group with "Id": "@ass1", "Name: "Bar", "scenario": "@scen1"
And events with:
------------------------------------------------------------------------
| Id | Kind | Message | Parent type | Parent | Actor |
| @evt1 | created | It's a trap! | assumption | @ass1 | @currentUser |
| @evt2 | updated | Noooooooo! | assumption | @ass1 | @currentUser |
------------------------------------------------------------------------
Given events with:
------------------------------------------------------------------------
| Id | Kind | Message | Parent type | Parent | Actor |
| @evt1 | created | It's a trap! | assumption | @ass1 | @currentUser |
| @evt2 | updated | Noooooooo! | assumption | @ass1 | @currentUser |
------------------------------------------------------------------------
// Event
.given("events with\n$table", function (propsArrays) {
// Iterate over table rows
return propsArrays.map((props) => {
// Parse according to contract
const params = parseProps(props, {
id: { type: "id"},
kind: { type: "string" },
message: { type: "string" },
actor: { type: "record", model: "user" },
parentType: { type: "string" },
parent: { type: "id" },
});
// Destructure
let { id, kind, message, actor, parentType, parent } = params;
// Prepare missing data
parentType = pluralize(camelize(parentType));
parent = server.db[parentType].find(parent);
// Create record
return server.create("event", { id, kind, message, actor, parent });
});
})
Opinionated Cucumber
By Andrey Mikhaylov (lolmaus)
Opinionated Cucumber
- 590