Skip to main content

By Story

This grouping option is a concept that comes from the Behavior-Driven Development (BDD) methodology. Unlike the suite-based grouping, which is based on the technical structure of your test suite, the story-based grouping helps you to focus on the business value of your tests and view them from the end-user perspective.

Grouping by story

The story-oriented hierarchy has 4 mandatory levels: epic, feature, story and test case.

Glossary
Epic
High-level business goal
Feature
Functionality that delivers business value
Story
User story that describes a feature from the end-user perspective
Test Case
Atomic, lowest-level unit. In Jest, it is a single it or test function.

Before you start using this grouping option, you need to decide how exactly you want to implement it:

  • via annotations – a more granular approach, which allows you to control the grouping on a per-test basis;
  • via configuration – a quick option to enable it all at once, based on general rules;
  • via mixing these approaches – a compromise between the two, where the configuration serves as a fallback for missing annotations.

Using annotations​

The annotation-based approach gives you a fine-grained control over the names of your epic, feature and story labels, but it requires you to add annotations to every and each test case (sic!) which can be tedious.

Let's take the same project as in the previous article, where there are two parts: client and server. Both them deal with the same functionality – authentication and restoring forgotten passwords. Hence, it would make sense to group both client and server tests under the same epic named Authentication, and continue grouping them by features and stories regardless of the application layer.

login.test.js
/**
* @epic Authentication
* @feature Login screen
*/
describe('Login controller', () => {
it('should validate e-mail', () => {
/** @story Validation */
// ...
});

it('should return 401 if user is not found', () => {
/** @story Validation */
// ...
});
});

As mentioned before, the annotation-based approach requires you to annotate literally every test case with all the three labels (epic, feature and story), otherwise the report will be stubbornly displaying a flat structure in Behaviors section.

tip

To relax the requirement to annotate all your test cases, you can add a fallback via configuration, e.g.:

jest.config.js
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
testEnvironment: 'jest-allure2-reporter/environment-node',
reporters: [
'default',
[
'jest-allure2-reporter',
/** @type {import('jest-allure2-reporter').ReporterOptions} */
{
labels: {
epic: ({ value }) => value ?? 'Uncategorized',
feature: ({ value }) => value ?? 'Untitled feature',
story: ({ value }) => value ?? 'Untitled story',
},
},
],
],
};

Using configuration​

The configuration-based approach allows you to group test cases based on the available attributes like the test file path, the ancestor describe blocks and any other contextually available information.

It is much faster to implement than if you were to annotate every test case by hand, but it is also less flexible. Still, there are many cases where it can be useful, especially if you have a large test suite and you want to add some structure to it. For example, if your grouping by suite focuses mostly on the file structure, the story-based grouping may add "a fresh perspective" by grouping tests by describe blocks and test names, for example.

Let's explore a simple example, where we'll map:

  • epic to the top-level describe block
  • feature to the middle-level describe blocks
  • story to the lowest-level describe block
jest.config.js
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
testEnvironment: 'jest-allure2-reporter/environment-node',
reporters: [
'default',
['jest-allure2-reporter', /** @type {import('jest-allure2-reporter').ReporterOptions}*/ {
labels: {
epic: ({ testCase }) => testCase.ancestorTitles.at(0) ?? '(uncategorized)',
feature: ({ testCase }) => testCase.ancestorTitles.slice(1, -1).join(' > ') || '(uncategorized)',
story: ({ testCase }) => testCase.ancestorTitles.slice(2).at(-1) ?? '(uncategorized)',
},
}],
],
};

Many-to-many mapping​

It is worth mentioning that Allure allows you to map a test case to multiple epics, features and stories, but you should use this feature with caution, as it may lead to a very complex report structure.

login.test.js
it('should validate e-mail', () => {
/**
* @epic Authentication
* @feature Login screen
* @story Validation
*
* @epic Security
* @feature XSS prevention
* @story Login form
*/

// ...
});