Skip to content

require_mirror_test

v1.1.0WarningConfigurableTesting Rules

Warns when a library under lib/ has no matching test file:

lib/src/core/version.dart -> test/src/core/version_test.dart
lib/parser.dart -> test/parser_test.dart

This rule is in no preset. Whether a project mirrors its test tree is a project decision, so it must be enabled by name.

It cannot show a test was written first — that needs git history, and is gameable. It cannot show the test exercises anything — that is coverage’s job, and coverage has its own gaming problem.

What it shows is that no production file shipped without somebody creating a place for its tests. That is the load-bearing half in practice, because the common failure is not a weak test but no test at all. The message says a test file is missing, not that the code is untested: the rule cannot tell the difference and should not imply it can.

A script has to re-derive the mapping from a path string, and gets barrel detection wrong unless it reimplements a parser — the usual approximation greps for lines that are not blank, not a comment and not an export, which is close but not the same thing. This rule has the AST already, so “declares no public element” and “is a barrel” are exact rather than approximate. It also reports in the editor while the file is being written, which is when creating the test costs the least.

See also: Testing in Dart | package:test

// lib/src/core/version.dart — with no test/src/core/version_test.dart
class Version {
const Version(this.major, this.minor);
final int major;
final int minor;
}
lib/src/core/version.dart
class Version {
const Version(this.major, this.minor);
final int major;
final int minor;
}
test/src/core/version_test.dart
void main() {
test('parses a two-part version', () {
expect(Version.parse('1.2'), equals(const Version(1, 2)));
});
}

These need no configuration, and are the exclusions that make the rule usable:

  • Generated files.g.dart, .freezed.dart, .gr.dart, .gen.dart, .config.dart, .mocks.dart, .pb.dart, and anything under generated/.
  • Barrel files — detected from the AST rather than the filename, because a barrel is not always named after its directory. A file that exports and declares is not a barrel: it has code of its own to test.
  • Files declaring nothing public — a file of private helpers for its library has no surface a test could target, and demanding one produces empty test files, which is worse than no rule at all.
  • Part files — their declarations belong to the library that declares them.

This rule is in no preset, so it does nothing until you name it:

many_lints.yaml
rules:
require_mirror_test: true

To turn it back off, set it to false or remove the block.

To keep the rule on but skip certain paths, use per-rule exclude.

analysis_options.yaml
many_lints:
rules:
require_mirror_test:
test_dir: test
suffix: _test
fallback_anywhere: true
exclude:
- 'lib/*.dart'
- '**/generated/**'
Option Type Default Description
test_dir string test The directory the mirrored path is rooted at
suffix string _test Appended to the file’s base name
fallback_anywhere bool true Accept a file of the right name anywhere under test_dir

fallback_anywhere matters more than it looks. Projects reorganise test trees, and a rule that fails because a test moved teaches people to switch the rule off. Set it to false only if the exact mirrored path is itself the convention you want enforced.