1.0.2 • Published 11 months ago

@muraldevkit/ds-exported-tests v1.0.2

Weekly downloads
-
License
https://www.mural...
Repository
-
Last release
11 months ago

Exported Tests

Exported Tests is a testing architecture which enables teams to write tests in a reusable and importable fashion. The tests can be used internally within a team or shared across teams to ensure consistency in implementation and to maintain a DRY (don't repeat yourself) code base. Teams who implement testing for a product's design system components can benefit greatly from using Exported Tests.

This documentation is written for a TypeScript implementation. However, since this package compiles to JavaScript it is possible to create the files with a .js file extension and remove the type definitions if you do not need to support TypeScript.

Table of contents

  1. What's included
    1. Parsers
    2. Helpers
  2. Installation
  3. Understanding the Exported Test structure
    1. Configurations
      1. Conditionals
      2. Selecting specific tests
  4. Generating your test files
    1. Creating the requirements test file
    2. Generating the steps
    3. Add steps to your requirements
    4. Running tests
  5. Example component

What's included

Parsers

At the core of Exported Tests is a consistent object structure that contains the bits and pieces of all your tests. This approach provides for robust flexibility for the end user through the use of testing framework parsers. These parsers take the Exported Test object structure and covert it into the desired framework's tests.

Currently, Mural's Exported Tests only contains a parser for Cypress environments, found in the ./src/parsers directory.

Helpers

While not required, it is recommended to document your test scripts in Gherkin. This will give you a clear outline for structuring your tests. To support this connection, Mural's Exported Tests package contains helper functions aligning to Gherkin keywords for writing the Exported Test structure. These helper functions can be found in the ./src/gherkinHelpers.ts file.

For defining the structure of your tests use the following helpers:

  • Background
  • Feature
  • Scenario

For defining individual test steps, the available helpers are:

  • Given
  • Then
  • When

If you're using TypeScript, you may also require some type definitions in your imports. The available type definitions which can be imported are:

  • ExportedTest
  • ExportedTestStep
  • ExportedTestSuite

Installation

In order to use this package, it needs to be added to your package as a devDependency:

npm install -save-dev @muraldevkit/ds-exported-tests

Understanding the Exported Test structure

Because Mural's tests align with the Gherkin requirements, we use related terminology within our object key naming.

In its most basic form, an exported test is an array of a test groupings. Consider the following example:

[
	{
		feature: 'description of a group of tests',
		tests: [
			{
				scenario: 'description of the test',
				steps: [
					{
						type: '' // The test step keyword (e.g. the Gherkin helper function name),
						req: 'description of the step'
						def: () => {...} // Actual test implementation
					},
					// ...
				]
			},
			// ...
		]
	},
	// ...
]

This can alternatively be written by our helpers as:

[
	Feature('description of a group of tests', {
		{
			scenarios: {
				testID: Scenario('description of the test',
					When('description of the step', () => {...})
					Then('description of the step', () => {...})
					// ...
				),
				// ...
			},
		}
	}),
	// ...
]

Configurations

While this isn't that exciting, this is only the beginning. What makes Exported Tests robust is the ability to configure them so that they work in a variety of use cases.

Conditionals

The first configuration that is built in to the Exported Test architecture is a conditional. This allows the tests to review criteria and determine if the tests (or optional alternative tests) should be executed for your use case.

Currently, this is supported at the suite-level using our helpers:

[
	Feature('description of a group of tests', {
		{
			background: Background({...args},
				Given('description of conditional',
					(...args) => {return true || false})
			),
			scenarios: {
				testID: Scenario('description of the test',
					When('description of the step', () => {...})
					Then('description of the step', () => {...})
					// ...
				)
				// ...
			},
			altScenarios: { // this is an optional configuration and if omitted, no tests are ran for the given feature
				testID: Scenario('description of the test if conditional is false',
					When('description of the step', () => {...})
					Then('description of the step', () => {...})
					// ...
				)
				// ...
			}
		}
	}),
	// ...
]

Selecting specific tests

At times, you may only want to include only a specific test from the suite. You can do this by passing the ID of the test to the feature so that it only executes that test at run time. If omitted, all tests in the suite are run. In the following example, only the test with the ID foo will be executed.

[
	Feature('description of a group of tests', {
		{
			scenarios: {
				testID: Scenario('description of the test',
					When('description of the step', () => {...})
					Then('description of the step', () => {...})
					// ...
				),
				foo: Scenario('description of the test',
					When('description of the step', () => {...})
					Then('description of the step', () => {...})
					// ...
				),
				// ...
			},
		},
		['foo']
	}),
	// ...
]

Generating practical test files

While you can write your tests as previously described, they aren't very practical. We recommend using JavaScript functions to make the tests more robust. We do this by creating a new TypeScript file that outlines the requirements being tested and exports a class which contains static methods for each Feature.

Lastly, you'll want to use a constructor to return an array of test suites for any given set of requirements. By using this architecture, we allow additional configurations to be passed in via the constructor. These configurations are unique to the tests being written.

UI Test recommendation: When testing UI elements, allow for a custom CSS selector to grab the root of the UI piece/component being tested. This enables additional re-use opportunities within various product use cases.

import { Feature, Scenario, ...etc } from '@muraldevkit/ds-exported-tests';
export default class SampleTests {
	testsToExecute = [];

	static exampleFeature(
		settings: Record<string, string>,
		include?: string[]
	): ExportedTestSuite[] {
		// For now, create an empty object. We will add steps next
		const scenarios = {};

		return Feature('Example Feature title', { scenarios }, include);
	}

	// ...

	constructor(demoConfig: Record<string, string>) {
	const defaults = {
		// you can define any defaults to pass to the configuration here
	};
	const config = Object.assign({}, defaults, demoConfig);
	this.testsToExecute = this.testsToExecute
		// concat each Feature to the array here
		.concat(SampleTests.exampleFeature(config));
}
}

Generating the steps

While you can define your steps from within each static method previously described, we actually recommend defining each step in an external file. We recognize the tediousness of creating the JavaScript class and plan to build an automation process for creating this file in the future. Separating your steps now helps prepare you for that future!

Start by importing the Gherkin helper functions related to the steps (e.g. Given, When, Then) you need into your file and pass the step a test function using your testing library.

In this example, we are using Cypress and creating a separate steps.ts file:

import { When } from '@muraldevkit/ds-exported-tests';
// A step to access an element and save it as a variable (`myElement`)
export const accessElem = (selector: string): ExportedTestStep => When(
	'I access the element',
	() => {
		cy.get(selector)
			.as('myElement');
	}
);

We do not show a lot of detail with this phase as it's unique to your testing framework and use case.

Add steps to your requirements

Now that you've written the steps for your requirements, you can import them into the original file.

import * as steps from './steps';

Then, replace the empty object in your static method with the steps you just created.

// ...
static exampleFeature(
	settings: Record<string, string>,
	include?: string[]
): ExportedTestSuite[] {
	const scenarios = {
		base: Scenario('Scenario 1',
			steps.accessElem(settings.selector),
			steps.findContent
		)
	};
	return Feature('Example Feature title', { scenarios }, include);
}
// ...

Running tests

Once you have added your steps into your requirements file, you're ready to test. To run Exported Tests, you'll need to import the appropriate test parser into your testing framework.

Pass the Exported Tests you created into your test parser, and the tests should run in the correct testing framework. The following example shows a basic Cypress setup:

import { cypressParser } from '@muraldevkit/ds-exported-tests';
import { default as SampleTests } from '../your/requirements/file';

// Create a new instance of exported tests with your custom configuration to
// grab the specific element being tested
const myTests = new SampleTests({selector: '#my-element'});
describe('USE_CASE_DESCRIPTION', () => {
	// Visit the URL for the test
	before(() => {
		cy.visit('URL_TO_TEST');
	});
	cypressParser(myTests);
});

Example component

For a complete code example, check out the example files located in the example directory in this package.

1.0.2

11 months ago

1.0.1

1 year ago

1.0.0

1 year ago