Basics
This check is disabled by default.
Learn how to enable it via .credo.exs.
This check has a base priority of high and works with any version of Elixir.
Explanation
Tests must exercise behaviour through the public API, not inspect the TEXT or AST of production source files.
Why
A "contract test" that File.read!s a lib/*.ex module and asserts on its
source with =~ / Regex / Code.string_to_quoted is brittle. It breaks
on behaviour-preserving refactors (a case becoming multi-clause function
heads, a formatter reflow, a rename) even though the runtime behaviour is
unchanged, and it gives false confidence: a matched substring may be a
comment or a shadowed clause, and a passing grep never actually ran the
code. AST inspection of the same source (Code.string_to_quoted +
Macro walking) has the same fault: it asserts on structure, not behaviour.
Test the real contract instead. Call the producer to get each output shape,
call the consumer with each shape, and assert the returned value or effect.
If the code under test is private, make it public (a @doc false function
is fine) or extract a reactor closure into a named public function.
Exposing a function to test critical behaviour is always preferable to
asserting on source text.
Bad
# A literal source read.
@impl_source "lib/forge_symphony/.../implementation.ex"
source = File.read!(@impl_source)
assert source =~ "%StepFailure{}"
# Source paths carried as DATA, then read + parsed dynamically. Same
# anti-pattern, one indirection removed.
@registry [%{id: :x, file: "lib/forge_symphony/.../step.ex"}]
defp source_ast(rel), do: rel |> File.read!() |> Code.string_to_quoted!()Good
assert {:ok, %{status: :failed, error: :boom}} =
Implementation.validate_dispatch_result(%StepFailure{error: :boom}, ...)What is flagged
Within test files (included_paths, default [~r"_test\.exs$"]):
- a module attribute whose value IS a
lib/*.expath (@impl_source "lib/.../x.ex"); File.read!/File.read/File.stream!with a literallib/*.exargument;Code.string_to_quoted(and the!,_with_comments,eval_string,eval_filevariants) on a literallib/*.expath, OR anywhere in a file that also carrieslib/*.expaths as data -- the dynamic-path form that reads a table of source files and parses each.
Carrying a lib/*.ex path as plain data (a routing-decision fixture, an
ADR-rules JSON blob) is NOT flagged on its own: only reading or parsing
production source is. Reading generated or fixture files (paths that do not
point at lib/*.ex) is not flagged, and parsing that only ever touches
non-lib source (a meta-lint over the test tree) is left alone.
Check-Specific Parameters
Use the following parameters to configure this check:
:included_paths
Paths (regexes or substrings) the check runs on (default: *_test.exs).
This parameter defaults to [~r/_test\.exs$/].
General Parameters
Like with all checks, general params can be applied.
Parameters can be configured via the .credo.exs config file.