TikTok for Developers
Make your tests readable with jest-bdd-generator
by Shiguang Ai, Software Engineer, TikTok
Tech @ TikTok
Open source

In this post, TikTok engineer Shiguang Ai introduces "jest-bdd-generator," a new Jest plugin designed to integrate Behavior-driven development (BDD) with existing testing practices. This tool enhances test readability by allowing developers to incorporate Gherkin-style specifications into their Jest tests. Read on to discover inspiration behind its creation and how it can transform your testing approach.
If you work in frontend, you've probably heard of Jest. Maybe you've also heard of Behavior-driven development (BDD), a different philosophy of testing. In this post, I'll demo a new Jest plugin developed at TikTok that integrates these two paradigms, plus some insights that make jest-bdd-generator different from previous attempts at combining them.

Why use BDD?

Although Jest incorporates some declarative expressions such as describe() and expect(), fundamentally, Jest tests are computer programs that perform operations and check their results. Engineers often write tests in a hurry, distracted by other problems like feature debugging or mocking complex systems. As a result, test code often gets messy. This hinders collaboration with colleagues, managers, or clients who rely on tests to express requirements and assess correctness.

Behavior-driven development doesn't fundamentally change how tests are run; instead, it adds a domain-specific language (DSL) that enables requirements to be described clearly using plain English. The most popular BDD framework is Cucumber, and its syntax is called Gherkin. This "executable specification" ensures that requirements are communicated effectively. Unlike mere documentation, executing the specification guarantees that it is always accurate and up-to-date. It also enables the generation of automated reports and summaries.

Some challenges with adoption

Cucumber provides official support for the JavaScript language, but many frontend codebases have nontrivial existing investments in Jest, a different test framework. Jest has gained widespread adoption through its support for React and ReactNative, easy onboarding from legacy JavaScript test frameworks, and extensive integration with toolchains and code coverage dashboards.

If switching to Cucumber would be too disruptive for your team, an alternative is to extend Jest to run Gherkin tests alongside Jest tests. We experimented with several NPM packages that accomplish this. However, while you get a unified test runner, managing the new format alongside the old can double the complexity of the developer experience. Our teams were reluctant to adopt it.

A new idea: roundtrip syntax transformations

This led us to consider a different approach: What if we implemented the Gherkin paradigm directly in ordinary Jest tests? By doing so, we can superimpose the benefits of BDD without changes to existing test APIs. But how would this work? Since Gherkin specifications are documentation-like, it makes sense to store them as code comments. We can use TypeScript syntax analysis to reintroduce DSL validation and reporting benefits.

As a simple example, suppose you have existing Jest tests for Math functions like ceiling, floor, and round, but are unfamiliar with the Gherkin representation. You can simply add comments like //@Given, //@When, //@Then to indicate these specification roles:


my-project/src/tests/math.test.ts

import { describe, test, expect } from '@jest/globals';

describe('Rounding methods of Math', () => {
  test.each([
    { num: 1234.1, method: 'ceil', result: 1235 },
    { num: 1234.9, method: 'ceil', result: 1235 },
    { num: -1234.1, method: 'ceil', result: -1234 },
    { num: -1234.9, method: 'ceil', result: -1234 },
    { num: 1234.1, method: 'floor', result: 1234 },
    { num: 1234.9, method: 'floor', result: 1234 },
    { num: -1234.1, method: 'floor', result: -1235 },
    { num: -1234.9, method: 'floor', result: -1235 },
    { num: 1234.5, method: 'round', result: 1235 },
    { num: 1234.4, method: 'round', result: 1234 },
    { num: -1234.5, method: 'round', result: -1234 },
    { num: -1234.6, method: 'round', result: -1235 }
  ])('Integer pattern', async ({ num, method, result }) => {
    //@Given input number is <num>
    expect(typeof num).toBe('number');

    //@When rounding method is <method>
    expect(Math).toHaveProperty(method);

    //@Then rounded number is <result>
    expect(Math[method](num)).toEqual(result);
  });
});

Generating comment stubs: gen-comments

To help with this, the jest-bdd-generator SDK provides a gen-comments command that automatically injects stubs for these comments. The shell command is shown below. (Bash's \ operator is used to split the input lines for readability.)

gen-comments pathTestsInput=./src/tests/math.test.ts \
  pathOutput=./src/tests/math-commented.test.ts

Jest to Gherkin: gen-doc

From the file shown above, we can use the gen doc command to generate a Gherkin specification based on our Jest test cases. It is invoked like this:

gen-doc pathTestsInput=./src/tests/math.test.ts \
  pathOutput=./docs/features/math.feature

The generated output file is shown below:


my-project/docs/features/math.feature

@format-feature
Feature: Rounding methods of Math

Scenario Outline: Integer pattern
  Given input number is <num>
  When rounding with <method>
  Then rounded number is <result>

Examples:
| num | method | result |
| 1234.1 | "ceil" | 1235 |
| 1234.9 | "ceil" | 1235 |
| -1234.1 | "ceil" | -1234 |
| -1234.9 | "ceil" | -1234 |
| 1234.1 | "floor" | 1234 |
| 1234.9 | "floor" | 1234 |
| -1234.1 | "floor" | -1235 |
| -1234.9 | "floor" | -1235 |
| 1234.5 | "round" | 1235 |
| 1234.4 | "round" | 1234 |
| -1234.5 | "round" | -1234 |
| -1234.6 | "round" | -1235 |


Gherkin to Jest: gen-test

Once engineers gain familiarity with the Gherkin format, they can shift to directly authoring specification files such as math.feature. Here, jest-bdd-generator can be invoked in the opposite direction, updating Jest files to match the specification. The command line looks like this:

# Save a backup copy of the test file
mv src/tests/math.test.ts src/tests/math-orig.test.ts

# Regenerate the test file
gen-test pathTestsInput=src/tests/math-orig.test.ts \
  pathGherkinInput=docs/features/math.feature \
  pathOutput=src/tests/math.test.ts

This approach allows engineers to interchangeably work on both math.feature and math.test.ts, automatically syncing changes in either direction to ensure that the readable English specification is kept consistent with its program code implementation.

Test reporting

The BDD formalism also provides an opportunity for more readable reporting on what is being tested.

gen-report pathTestsInput=src/tests/math-orig.test.ts \
  pathFeatureInput=docs/features/math.feature

The output is a Cucumber report detailing the test results, including call stacks and diagnostic details for any failed tests:

Give behavior driven development a try!

This provides a quick overview of the concept and motivation behind jest-bdd-generator. More detailed usage instructions can be found on the jest-bdd-generator project website, including other interesting features such as the experimental Test Oracle server. If you have any questions, you can create an issue in our GitHub repository: https://github.com/tiktok/jest-bdd-generator

Share this article
Discover more
TikTok for Developers Is Now on YouTube!
We're launching our official TikTok for Developers YouTube channel for our community to explore TikTok's tools, APIs, and developer insights, including exclusive content from TikTok DevDay.
Community
Introducing TikTok Research API Wrappers on GitHub
Check out TikTok's new Research API Wrappers, which make it easier for researchers of all technical skill levels to use TikTok's Research API
Research
Developer products
Highlights from our Privacy Innovation Meetup at ACM CCS 2024
TikTok's Privacy Innovation team hosted a meetup at ACM CCS 2024, showcasing privacy-preserving technologies like ManaTEE and reinforcing the team's commitment to privacy and security through industry and academic collaboration.
Privacy
Community