What does it mean to test behavior rather than implementation details? Why does it matter?
The single most important habit for tests that survive refactors and actually catch regressions.
Testing BEHAVIOR means asserting on what the code does from the outside — its observable outputs and effects for given inputs — rather than HOW it does it internally. Implementation details are the internals a consumer shouldn't care about: private methods, internal state variable names, which helper was called, the exact DOM structure or CSS class, how many times a function ran. A behavior test says 'given this input, the user sees this result'; an implementation test says 'the component called setState twice and has a state field named _count'. Why it matters: tests coupled to implementation break when you REFACTOR — change the internals while keeping behavior identical and the test fails, even though nothing a user cares about changed. That's a false alarm that trains people to distrust and ignore the suite, and it makes refactoring painful, discouraging the very cleanup that keeps code healthy. Worse, implementation tests can PASS while the feature is broken (you asserted a method was called, but its result was wrong). The rule of thumb: 'the more your tests resemble the way your software is used, the more confidence they give you' — so query the UI the way a user does (by role, label, visible text), assert on rendered output and side effects, and avoid reaching into private state or spying on internal calls unless the call itself IS the contract (e.g., 'it must call the payment API exactly once'). A practical test: if I rewrite the internals but keep the same public behavior, should this test still pass? If yes, it's a behavior test; if it would break, it's testing implementation.
Deciding what to assert; explaining why a test broke on a refactor that changed no behavior.
// Implementation detail: couples to internal state -> breaks on refactor
expect(wrapper.state('isOpen')).toBe(true);
expect(instance._handleClick).toHaveBeenCalled();
// Behavior: what the user actually observes -> survives refactors
await user.click(screen.getByRole('button', { name: /open menu/i }));
expect(screen.getByRole('menu')).toBeVisible();
// Ask: if I rewrite the internals but keep behavior,
// should this test still pass? If no -> it's testing implementation.