Installs into .claude/skills of the current project.
Are you the author of In Elixir Testing?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/matthewsinclair-in-elixir-testing)
---
description: "Elixir testing rules: strong assertions, no control flow in tests, async by default, real code over mocks, Highlander for tests"
rules:
- IN-EX-TEST-001
- IN-EX-TEST-002
- IN-EX-TEST-003
- IN-EX-TEST-004
- IN-EX-TEST-005
- IN-EX-TEST-006
- IN-EX-TEST-007
---
# Elixir Testing Essentials
Load the Intent Elixir test-rule pack into context. Invoke before writing ExUnit tests or during code review of test code.
**NEVER worry about test counts.** Do not count tests, do not report coverage percentages, do not set numeric targets. Quality over quantity, always. One strong test that proves a domain invariant is worth more than twenty shape tests that pass for any implementation.
## Procedure
### 1. Load the test rules
Read each rule on demand when the situation matches. The full text is served by the installed Intent tool via `intent claude rules show <id>`.
| Rule ID | Slug | What it enforces |
| ---------------- | ------------------------------ | --------------------------------------------------------------------- |
| `IN-EX-TEST-001` | `strong-assertions` | Assert concrete values, never shape alone (`is_struct`, `is_map`). |
| `IN-EX-TEST-002` | `no-process-sleep` | Synchronise by message or call; never by wall-clock sleep. |
| `IN-EX-TEST-003` | `async-by-default` | Every test module declares `async: true` unless state forces opt-out. |
| `IN-EX-TEST-004` | `start-supervised` | Use `start_supervised!/2`; bare `start_link` leaks processes. |
| `IN-EX-TEST-005` | `no-control-flow-in-tests` | Test bodies are straight-line; no `if`/`case`/`cond` inside. |
| `IN-EX-TEST-006` | `real-code-over-mocks` | Mock only at external boundaries; never your own modules. |
| `IN-EX-TEST-007` | `test-highlander-shared-setup` | No duplicated setup; fixtures with async-safe identity. |
Rules 002, 003 and 004 are upstream-derived from `elixir-test-critic` (each carries `upstream_id:` in its frontmatter). Full MIT attribution ships with the rule library at the Intent install (`rules/_attribution/elixir-test-critic.md`).
### 2. Additional operational conventions
Not yet first-class rules in the library:
- **One assertion focus per test.** Each test verifies one outcome. Name with `success:`, `failure:`, or `invariant:` prefix. If the test would need two `describe` blocks, it is two tests. (Implicit in IN-EX-TEST-001.)
- **Test the domain contract, not the implementation.** Test through public API (Ash code interfaces). Never test private functions. (Supports IN-EX-TEST-006.)
- **Spec-driven tests when a spec exists.** When `*_test.spec.md` exists, test names match spec assertions exactly -- no tests without a spec counterpart, no spec assertions without a test. (Diogenes-adjacent discipline.)
### 3. Run the tests often
Tests only protect against regressions if they are green. Run `mix test` after each meaningful change; stop and fix before moving on.
## Red Flags
| Rationalisation | Reality |
| --------------------------------------------------- | ----------------------------------------------------------------- |
| "Shape assertion is good enough to prove it works." | See IN-EX-TEST-001. Shape passes for any value of the right type. |
| "Process.sleep is the only way to sync this." | See IN-EX-TEST-002. There is always a call, message, or monitor. |
| "Sequential tests are fine for now." | See IN-EX-TEST-003. "For now" becomes "forever" once coupled. |
| "I'll mock Accounts to simplify this test." | See IN-EX-TEST-006. Use the real Accounts; mock only the mailer. |
| "Same 4 lines in every test is fine." | See IN-EX-TEST-007. Fixture drift is how tests start lying. |