Claude Code Plugins

Community-maintained marketplace

Feedback

elixir-tdd-enforcement

@mkreyman/bmad-elixir
0
0

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

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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

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

    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

    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

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

    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

    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

# 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

# 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

# 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.