DEV Community

Cover image for Frontend Testing Guide: 10 Essential Rules for Naming Tests
Alexander Opalic
Alexander Opalic

Posted on • Originally published at alexop.dev

12 1 1 1 2

Frontend Testing Guide: 10 Essential Rules for Naming Tests

Introduction

The path to better testing starts with something surprisingly simple: how you name your tests. Good test names:

  • Make your test suite more maintainable
  • Guide you toward writing tests that focus on user behavior
  • Improve clarity and readability for your team

In this blog post, we'll explore 10 essential rules for writing better tests that will transform your approach to testing. These principles are:

  1. Framework-agnostic
  2. Applicable across the entire testing pyramid
  3. Useful for various testing tools:
    • Unit tests (Jest, Vitest)
    • Integration tests
    • End-to-end tests (Cypress, Playwright)

By following these rules, you'll create a more robust and understandable test suite, regardless of your chosen testing framework or methodology.

Rule 1: Always Use "should" + Verb

Every test name should start with "should" followed by an action verb.

// ❌ Bad
it('displays the error message', () => {})
it('modal visibility', () => {})
it('form validation working', () => {})

// ✅ Good
it('should display error message when validation fails', () => {})
it('should show modal when trigger button is clicked', () => {})
it('should validate form when user submits', () => {})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern: should [verb] [expected outcome]

Rule 2: Include the Trigger Event

Specify what causes the behavior you're testing.

// ❌ Bad
it('should update counter', () => {})
it('should validate email', () => {})
it('should show dropdown', () => {})

// ✅ Good
it('should increment counter when plus button is clicked', () => {})
it('should show error when email format is invalid', () => {})
it('should open dropdown when toggle is clicked', () => {})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern: should [verb] [expected outcome] when [trigger event]

Rule 3: Group Related Tests with Descriptive Contexts

Use describe blocks to create clear test hierarchies.

// ❌ Bad
describe('AuthForm', () => {
  it('should test empty state', () => {})
  it('should test invalid state', () => {})
  it('should test success state', () => {})
})

// ✅ Good
describe('AuthForm', () => {
  describe('when form is empty', () => {
    it('should disable submit button', () => {})
    it('should not show any validation errors', () => {})
  })

  describe('when submitting invalid data', () => {
    it('should show validation errors', () => {})
    it('should keep submit button disabled', () => {})
  })
})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern:

describe('[Component/Feature]', () => {
  describe('when [specific condition]', () => {
    it('should [expected behavior]', () => {})
  })
})
Enter fullscreen mode Exit fullscreen mode

Rule 4: Name State Changes Explicitly

Clearly describe the before and after states in your test names.

// ❌ Bad
it('should change status', () => {})
it('should update todo', () => {})
it('should modify permissions', () => {})

// ✅ Good
it('should change status from pending to approved', () => {})
it('should mark todo as completed when checkbox clicked', () => {})
it('should upgrade user from basic to premium', () => {})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern: should change [attribute] from [initial state] to [final state]

Rule 5: Describe Async Behavior Clearly

Include loading and result states for asynchronous operations.

// ❌ Bad
it('should load data', () => {})
it('should handle API call', () => {})
it('should fetch user', () => {})

// ✅ Good
it('should show skeleton while loading data', () => {})
it('should display error message when API call fails', () => {})
it('should render profile after user data loads', () => {})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern: should [verb] [expected outcome] [during/after] [async operation]

Rule 6: Name Error Cases Specifically

Be explicit about the type of error and what causes it.

// ❌ Bad
it('should show error', () => {})
it('should handle invalid input', () => {})
it('should validate form', () => {})

// ✅ Good
it('should show "Invalid Card" when card number is wrong', () => {})
it('should display "Required" when password is empty', () => {})
it('should show network error when API is unreachable', () => {})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern: should show [specific error message] when [error condition]

Rule 7: Use Business Language, Not Technical Terms

Write tests using domain language rather than implementation details.

// ❌ Bad
it('should update state', () => {})
it('should dispatch action', () => {})
it('should modify DOM', () => {})

// ✅ Good
it('should save customer order', () => {})
it('should update cart total', () => {})
it('should mark order as delivered', () => {})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern: should [business action] [business entity]

Rule 8: Include Important Preconditions

Specify conditions that affect the behavior being tested.

// ❌ Bad
it('should enable button', () => {})
it('should show message', () => {})
it('should apply discount', () => {})

// ✅ Good
it('should enable checkout when cart has items', () => {})
it('should show free shipping when total exceeds $100', () => {})
it('should apply discount when user is premium member', () => {})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern: should [expected behavior] when [precondition]

Rule 9: Name UI Feedback Tests from User Perspective

Describe visual changes as users would perceive them.

// ❌ Bad
it('should set error class', () => {})
it('should toggle visibility', () => {})
it('should update styles', () => {})

// ✅ Good
it('should highlight search box in red when empty', () => {})
it('should show green checkmark when password is strong', () => {})
it('should disable submit button while processing', () => {})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern: should [visual change] when [user action/condition]

Rule 10: Structure Complex Workflows Step by Step

Break down complex processes into clear steps.

// ❌ Bad
describe('Checkout', () => {
  it('should process checkout', () => {})
  it('should handle shipping', () => {})
  it('should complete order', () => {})
})

// ✅ Good
describe('Checkout Process', () => {
  it('should first validate items are in stock', () => {})
  it('should then collect shipping address', () => {})
  it('should finally process payment', () => {})

  describe('after successful payment', () => {
    it('should display order confirmation', () => {})
    it('should send confirmation email', () => {})
  })
})
Enter fullscreen mode Exit fullscreen mode

Generic Pattern:

describe('[Complex Process]', () => {
  it('should first [initial step]', () => {})
  it('should then [next step]', () => {})
  it('should finally [final step]', () => {})

  describe('after [key milestone]', () => {
    it('should [follow-up action]', () => {})
  })
})
Enter fullscreen mode Exit fullscreen mode

Complete Example

Here's a comprehensive example showing how to combine all these rules:

// ❌ Bad
describe('ShoppingCart', () => {
  it('test adding item', () => {})
  it('check total', () => {})
  it('handle checkout', () => {})
})

// ✅ Good
describe('ShoppingCart', () => {
  describe('when adding items', () => {
    it('should add item to cart when add button is clicked', () => {})
    it('should update total price immediately', () => {})
    it('should show item count badge', () => {})
  })

  describe('when cart is empty', () => {
    it('should display empty cart message', () => {})
    it('should disable checkout button', () => {})
  })

  describe('during checkout process', () => {
    it('should validate stock before proceeding', () => {})
    it('should show loading indicator while processing payment', () => {})
    it('should display success message after completion', () => {})
  })
})
Enter fullscreen mode Exit fullscreen mode

Test Name Checklist

Before committing your test, verify that its name:

  • [ ] Starts with "should"
  • [ ] Uses a clear action verb
  • [ ] Specifies the trigger condition
  • [ ] Uses business language
  • [ ] Describes visible behavior
  • [ ] Is specific enough for debugging
  • [ ] Groups logically with related tests

Conclusion

Thoughtful test naming is a fundamental building block in the broader landscape of writing better tests. To maintain consistency across your team:

  1. Document your naming conventions in detail
  2. Share these guidelines with all team members
  3. Integrate the guidelines into your development workflow

For teams using AI tools like GitHub Copilot:

  • Incorporate these guidelines into your project documentation
  • Link the markdown file containing these rules to Copilot
  • This integration allows Copilot to suggest test names aligned with your conventions

For more information on linking documentation to Copilot, see:
VS Code Experiments Boost AI Copilot Functionality

By following these steps, you can ensure consistent, high-quality test naming across your entire project.

Image of Datadog

Measure and Advance Your DevSecOps Maturity

In this white paper, we lay out a DevSecOps maturity model based on our experience helping thousands of organizations advance their DevSecOps practices. Learn the key competencies and practices across four distinct levels of maturity.

Get The White Paper

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more