Creating Reusable Test IDs with BEM Methodology - squid-ware Developer Community

menu

articles

Creating Reusable Test IDs with BEM Methodology

Learn how to create maintainable and reusable test IDs using BEM naming conventions for robust and reliable testing. ~ Quido Hoekman

9 min read // October 10, 2025

Table of Contents

The Importance of Reliable Test IDs

In modern web development, automated testing is crucial for maintaining code quality and preventing regressions. However, tests that rely on CSS selectors, class names, or DOM structure are fragile and break easily when the UI changes. This is where test IDs come in—they provide a stable, semantic way to identify elements for testing purposes.

What Are Test IDs?

Test IDs are special attributes used to identify elements in the DOM specifically for testing. They should be implemented using the data-testid attribute, which clearly indicates their purpose and separates them from other data attributes.

<button data-testid="submit-button">Submit</button>
<div data-testid="user-profile">...</div>

Why BEM for Test IDs?

BEM (Block Element Modifier) is a CSS naming methodology that provides a clear, consistent structure for naming. When applied to test IDs, BEM offers several advantages:

  • Consistency - Predictable naming patterns across your entire application
  • Scalability - Easy to extend and maintain as your application grows
  • Clarity - Self-documenting names that clearly indicate component relationships
  • Team Alignment - Everyone follows the same naming conventions

BEM Test ID Structure

The BEM methodology for test IDs follows this pattern:

[component-name]__[sub-component-name]__[item-name]-[item-id]__[feature]--[condition]

Single Instance Components

For components that appear only once on a page, use simple BEM naming:

<!-- Card component -->
<div data-testid="card">
  <h2 data-testid="card__title">Product Name</h2>
  <p data-testid="card__description">Product description</p>
  <button data-testid="card__button">Add to Cart</button>
</div>

Multiple Instance Components

For components that appear multiple times (like list items), include a unique identifier:

<!-- List component with multiple items -->
<ul data-testid="list">
  <li data-testid="list__item-1">First item</li>
  <li data-testid="list__item-2">Second item</li>
  <li data-testid="list__item-3">Third item</li>
</ul>

Practical Examples

<nav data-testid="navigation">
  <ul data-testid="navigation__menu">
    <li data-testid="navigation__item-home">
      <a data-testid="navigation__link-home" href="/">Home</a>
    </li>
    <li data-testid="navigation__item-about">
      <a data-testid="navigation__link-about" href="/about">About</a>
    </li>
  </ul>
  <button data-testid="navigation__toggle">Menu</button>
</nav>

Form Component

<form data-testid="contact-form">
  <div data-testid="contact-form__field-group">
    <label data-testid="contact-form__label-name">Name</label>
    <input data-testid="contact-form__input-name" type="text" />
  </div>
  <div data-testid="contact-form__field-group">
    <label data-testid="contact-form__label-email">Email</label>
    <input data-testid="contact-form__input-email" type="email" />
  </div>
  <button data-testid="contact-form__submit">Send Message</button>
</form>

Component Design with Hierarchical Test IDs

One powerful approach to test ID design is leveraging parent data-testid attributes to create hierarchical specificity. This allows sub-components to inherit context from their parent components, making test IDs more descriptive and maintainable.

Parent-Child Test ID Relationships

When designing components, consider how child components can leverage their parent’s test ID for increased specificity:

<!-- Parent component with specific context -->
<div data-testid="card__pricing-low">
  <h3 data-testid="card__pricing-low__title">Basic Plan</h3>
  <p data-testid="card__pricing-low__description">Perfect for individuals</p>
  <div data-testid="card__pricing-low__features">
    <span data-testid="card__pricing-low__feature-1">5 Projects</span>
    <span data-testid="card__pricing-low__feature-2">1GB Storage</span>
  </div>
  <button data-testid="card__pricing-low__cta">Choose Plan</button>
</div>

Alternative Approach: Using within() for Component Scoping

Sometimes the test ID specificity isn’t enough, so we can use the within() function to scope the test assertions to a specific component. This is alternative if the hierarchical test ids are hard to implement.

Standardizing Test IDs

To ensure consistency across your application, it’s beneficial to create utility functions that automatically format and standardize test IDs. This approach helps maintain BEM conventions while reducing human error and ensuring uniform formatting.

Creating a Test ID Utility

Here’s a example of a vanilla JavaScript implementation:

/**
 * TestIdFormatter class
 * @class
 * @description Transforms input values into standardized test IDs
 */
class TestIdFormatter {
  /**
   * Transforms input values into standardized test IDs
   * @param {string|number|Array} value - The value(s) to transform
   * @returns {string} - Formatted test ID
   */
  transform(value) {
    if (value === null || value === undefined) {
      return "";
    }

    if (typeof value === "string" || typeof value === "number") {
      return this.formatValue(value.toString());
    }

    if (Array.isArray(value)) {
      return value
        .map((item) => {
          if (item === null || item === undefined) {
            return "";
          }
          return this.formatValue(item.toString());
        })
        .join("__")
        .toLowerCase();
    }

    return "";
  }

  /**
   * Formats individual values for test ID usage
   * @param {string} value - The value to format
   * @returns {string} - Formatted value
   */
  formatValue(value) {
    return value
      .replace(/\s+/g, "-") // Replace spaces with hyphens
      .replace(/[\(\[\{]/g, "--") // Replace opening brackets with double hyphens
      .replace(/[\)\]\}]/g, "") // Remove closing brackets
      .replace(/[^a-zA-Z0-9-_]/g, ""); // Remove special characters except hyphens and underscores
  }
}

// Usage examples
const testIdFormatter = new TestIdFormatter();

// Simple string formatting
console.log(testIdFormatter.transform("User Profile")); // "user-profile"
console.log(testIdFormatter.transform("Product (Premium)")); // "product--premium"

// Array formatting for hierarchical IDs
console.log(testIdFormatter.transform(["card", "pricing", "low"])); // "card__pricing__low"
console.log(testIdFormatter.transform(["user", "profile", "edit-button"])); // "user__profile__edit-button"

// Mixed types
console.log(testIdFormatter.transform(["product", "card", 123])); // "product__card__123"

Conclusion

Implementing BEM methodology for test IDs creates a robust foundation for automated testing. By following consistent naming conventions, you’ll build tests that are easier to maintain, understand, and extend. Start implementing these patterns in your next project, and you’ll see the benefits in both test reliability and team productivity.

Remember: good test IDs are an investment in your application’s long-term maintainability and your team’s development velocity.

Resources and References