The test name is documentation
When a test fails in CI three months later, the name is all you see in the report before opening the code. "should reject login with invalid password" communicates intent, context and expected outcome in one line. "test1" or "testLogin" communicate nothing.
The "should..." pattern
Used in Mocha, Jest, Vitest, RSpec. Structure: describe("<Subject>", () => {
it("should <expected behavior> when <condition>", ...) }). Third-person
verb emphasizes that you're describing system behavior, not the developer's action.
Given/When/Then (BDD)
Pattern from Cucumber and Gherkin. Given describes the initial state, When the action taken, Then the expected outcome. Works very well for integration and e2e tests: "Given an empty cart, when the user adds a product, then the cart shows 1 item".
The JUnit pattern
In Java, the classic convention is methodName_stateUnderTest_expectedBehavior.
Example: login_invalidPassword_returns401. Lets you sort tests
alphabetically and group them visually. Kotlin uses backticks for spaces:
fun \`returns 401 when password is invalid\`().
Snake case in Python
Pytest encourages descriptive snake_case names:
def test_login_returns_401_when_password_is_invalid():. Long but clear. Pairs
well with parametrize for multiple cases.
What to avoid
- Generic names: "testLogin", "test1", "happyPath".
- Implementation details in the name: "shouldCallSpyWithObject" (breaks on refactor).
- Double negatives: "should not fail when not invalid".
- ALL CAPS or runOnCamelCase names.
Structure: AAA
Arrange-Act-Assert is the classic pattern for the test body. Arrange: set up state and mocks. Act: execute the action under test. Assert: verify the outcome. A test should have one Act; if you see two, they're probably two tests.
Parametrized tests
To cover multiple inputs, don't copy the test five times. Use test.each in
Jest, parametrize in pytest, theory in xUnit. Names are
auto-generated from parameters, keeping readability without duplication.