Agent skill

elixir-tdd-enforcement

MANDATORY for ANY feature or bugfix - write ExUnit test FIRST, watch it FAIL, then implement. NO exceptions. Use before writing any Elixir production code.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/testing/elixir-tdd-enforcement-mkreyman-bmad-elixir

SKILL.md

Elixir TDD Enforcement: The Iron Law

THE IRON LAW

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

Not sometimes. Not usually. ALWAYS.

If you write production code before a failing test, DELETE IT and start over.

WHEN THIS SKILL APPLIES

  • Implementing ANY new function
  • Fixing ANY bug
  • Adding ANY feature
  • Modifying ANY behavior
  • Refactoring ANY code

If you're changing .ex files in lib/, this skill is MANDATORY.

THE RED-GREEN-REFACTOR CYCLE

Phase 1: RED (Write Failing Test)

  1. Write ONE minimal ExUnit test

    elixir
    test "creates user with valid attrs" do
      attrs = %{name: "Alice", email: "alice@example.com"}
      assert {:ok, %User{} = user} = Accounts.create_user(attrs)
      assert user.name == "Alice"
      assert user.email == "alice@example.com"
    end
    
  2. Run the test

    bash
    mix test test/my_app/accounts_test.exs:42
    
  3. VERIFY IT FAILS FOR THE RIGHT REASON

    • Read the error message
    • Confirm it's failing because functionality doesn't exist
    • NOT because of syntax errors or wrong test setup

CHECKPOINT: If test doesn't fail, delete it and write a different test.

Phase 2: GREEN (Minimal Implementation)

  1. Write SIMPLEST code to pass the test

    elixir
    def create_user(attrs) do
      %User{}
      |> User.changeset(attrs)
      |> Repo.insert()
    end
    
  2. Run the test again

    bash
    mix test test/my_app/accounts_test.exs:42
    
  3. VERIFY IT PASSES

    • Read the actual output
    • See the green dot or "1 test, 0 failures"
    • NOT just assume it works

CHECKPOINT: If test doesn't pass, fix implementation (not test).

Phase 3: REFACTOR (Improve While Green)

  1. Improve code quality

    • Extract functions
    • Improve names
    • Add pattern matching
  2. Run tests after EACH change

    bash
    mix test
    
  3. Stay GREEN

    • If tests fail during refactor, undo
    • Only refactor when all tests pass

CHECKPOINT: Tests must stay green throughout refactoring.

VERIFICATION CHECKLIST

Before claiming you're done, verify:

  • I wrote the test BEFORE any implementation code
  • I watched the test FAIL for the right reason
  • I read the actual failure message
  • I implemented only enough code to pass the test
  • I ran the test again and saw it PASS
  • I read the actual success message
  • All other tests still pass
  • I refactored only while tests were green

If you can't check ALL boxes, you didn't follow TDD.

COMMON VIOLATIONS AND RESPONSES

Violation: "I'll just write the code, then write the test"

Response: NO. Delete the code. Write test first.

Violation: "The function is simple, I don't need to see it fail"

Response: WRONG. Even simple code needs failing tests. Write test, watch fail.

Violation: "I already know what the test will look like"

Response: Irrelevant. Write it first anyway.

Violation: "I wrote the test and implementation together"

Response: Delete both. Write test, watch fail, then implement.

Violation: "The test passed on first run"

Response: RED FLAG. Test might not be testing anything. Review test.

Violation: "I'm just refactoring, I don't need new tests"

Response: Correct - but ALL existing tests must stay GREEN.

ELIXIR-SPECIFIC TEST PATTERNS

Testing Context Functions

elixir
# RED: Write test first
test "list_users/0 returns all users" do
  user1 = fixture(:user)
  user2 = fixture(:user)
  users = Accounts.list_users()
  assert length(users) == 2
  assert user1 in users
  assert user2 in users
end

# Run test → watch it fail (function doesn't exist)

# GREEN: Implement
def list_users do
  Repo.all(User)
end

# Run test → watch it pass

Testing Changesets

elixir
# RED: Write test for validation
test "changeset with invalid email" do
  changeset = User.changeset(%User{}, %{email: "invalid"})
  refute changeset.valid?
  assert %{email: ["invalid format"]} = errors_on(changeset)
end

# Run test → watch it fail

# GREEN: Add validation
def changeset(user, attrs) do
  user
  |> cast(attrs, [:email])
  |> validate_format(:email, ~r/@/)
end

Testing Phoenix Controllers

elixir
# RED: Write test
test "GET /users returns 200", %{conn: conn} do
  conn = get(conn, ~p"/users")
  assert html_response(conn, 200)
end

# Run test → watch it fail (route doesn't exist)

# GREEN: Add route and controller action

DIALYZER ERRORS: SPECIAL CASE

If Dialyzer reports an error:

  1. Write a test that exercises the problematic code
  2. Make sure test passes (proving code works)
  3. Add @spec to guide Dialyzer
  4. Run mix dialyzer to verify

NEVER:

  • Add to dialyzer.ignore
  • Modify dialyzer PLT to suppress
  • Comment out the code

The test proves it works. The spec helps Dialyzer understand.

CREDO WARNINGS: SPECIAL CASE

If Credo reports a warning:

  1. Understand WHY it's warning
  2. Fix the actual issue (complexity, style, etc.)
  3. Run mix credo to verify

NEVER:

  • Add to .credo.exs disabled list
  • Use inline # credo:disable-for-this-file
  • Ignore the warning

Credo is helping you write better code. Listen to it.

THE DISCIPLINE

TDD feels slow at first. That's because you're used to:

  • Writing code fast (then debugging for hours)
  • Skipping tests (then breaking things in production)
  • Guessing if it works (then finding out it doesn't)

TDD is actually faster because:

  • Tests catch bugs immediately
  • You know exactly what to implement
  • Refactoring is safe
  • Code works the first time

ENFORCEMENT

Before writing ANY Elixir production code, ask:

  1. "Have I written a failing test for this?"
  2. "Have I actually RUN the test and seen it fail?"
  3. "Do I know WHY it's failing?"

If any answer is NO → write the test first.

REMEMBER

"Tests that pass on the first run might not be testing anything."

"Code without a failing test first is guess-driven development."

"TDD is slow. Debugging untested code is slower."

THE RULE

RED → GREEN → REFACTOR

Not GREEN → RED → "oops"

Not WRITE → PRAY → DEBUG

RED → GREEN → REFACTOR

Every. Single. Time.

Didn't find tool you were looking for?

Be as detailed as possible for better results