Skip to main content Accessibility Feedback

You're doing JavaScript testing wrong

I have a confession: for most of my career, I’ve hated testing.

But I recently learned that’s because I’ve been doing it wrong, and maybe you have, too. In this video, I show you…

  1. The “right way” to approach testing.
  2. What I got wrong about TDD all these years.
  3. How to make testing easier, simpler, and more useful.

If you’re not someone who likes watching videos to learn (👋 hello, my fellow millennials and older!), here’s the quick version in text form.

Testing sucks because you try to test implementation details. This results in you writing tests to pass your code. That’s not useful, because…

  • Your code might not be doing something it should.
  • Your code might be doing something it shouldn’t.
  • Refactors require you to rewrite tests.
  • You’re less likely to catch critical errors.

Instead, you should…

  1. Write an empty test, with one comment for each external behavior your code should display.
  2. Write the code to test each comment below it.
  3. Then write the actual code to make it so.

For example, if I’m testing a disclosure component, here’s the pseudo-code for my tests…

// 1. The content is hidden

// 2. The toggle has the correct ARIA

// 3. Clicking the button should show the content
// and update the [aria-expanded] attribute

// 4. Clicking the button again should hide the content
// and update the [aria-expanded] attribute

// 5. Focusing on the button and pressing space should show the content
// and update the [aria-expanded] attribute

// 6. Focusing on the button and pressing enter/return should hide the content
// and update the [aria-expanded] attribute

And here’s the actual test code…

// Get the elements
const btn = page.getByRole('button');
const content = page.locator('#content');

// The content is hidden
await expect(content).not.toBeVisible();

// The toggle has the correct ARIA
await expect(btn).toHaveAttribute('aria-expanded', 'false');
await expect(btn).toHaveAttribute('aria-controls');

// Clicking the button should show the content
// and update the [aria-expanded] attribute
await btn.click();
await expect(content).toBeVisible();
await expect(btn).toHaveAttribute('aria-expanded', 'true');

// Clicking the button again should hide the content
// and update the [aria-expanded] attribute
await btn.click();
await expect(content).not.toBeVisible();
await expect(btn).toHaveAttribute('aria-expanded', 'false');

// Focusing on the button and pressing space should show the content
// and update the [aria-expanded] attribute
await btn.focus();
await btn.press(' ');
await expect(content).toBeVisible();
await expect(btn).toHaveAttribute('aria-expanded', 'true');

// Focusing on the button and pressing enter/return should hide the content
// and update the [aria-expanded] attribute
await btn.press('Enter');
await expect(content).not.toBeVisible();
await expect(btn).toHaveAttribute('aria-expanded', 'false');

When I run my test for the first time, it should fail. If not, I’m not testing the right things.

Then, I can write code until all tests pass.

Playwright is awesome, runs real browsers, and makes testing both functions and components really easy!

Here’s the source code from the video.