internal/watcher/watcher_test.go defines mockResolver, a stand-in for the DNSResolver interface that returns canned records, and every watcher test runs against it. That is a stubbed lookup. sneak's rule (2026-08-07, recorded on #94 and in the README section "No DNS mocking. Ever.", restated on #93) is that DNS is never mocked, not in tests and not anywhere else.
Two documents still invite it: TESTING.md says "the mock constructor exists for unit-testing other packages that consume the resolver", and the comment on NewFromLoggerWithClient in internal/resolver/resolver.go calls it "useful for testing with mock DNS responses".
What to do
Remove mockResolver. Watcher tests that need DNS use the real resolver against live nameservers, with the robustness the resolver tests already use (retries, several nameservers, timeouts).
Behaviour that depends on records changing (record changes, nameserver changes, inconsistencies) is tested by preparing the saved state the watcher starts from and letting live DNS supply the current records, or by testing the comparison on record data directly with no lookup involved. Never through a stand-in resolver.
Stand-ins for the notifier, port checker and TLS checker may stay: they are not DNS.
Remove the TESTING.md exception so it matches the README. Correct the resolver.go comment; if NewFromLoggerWithClient has no use other than mocks, remove it.
Definition of done
No mock, fake or stub of DNS resolution anywhere in the tree, tests included.
Watcher tests still cover first-run baseline, record change, nameserver change, nameserver failure and recovery, port state change and TLS expiry warnings.
TESTING.md, the README and code comments state the same rule.
The suite stays within its time limit; make check green.
Model: opus-5-5
`internal/watcher/watcher_test.go` defines `mockResolver`, a stand-in for the `DNSResolver` interface that returns canned records, and every watcher test runs against it. That is a stubbed lookup. sneak's rule (2026-08-07, recorded on https://git.eeqj.de/sneak/dnswatcher/issues/94 and in the README section "No DNS mocking. Ever.", restated on https://git.eeqj.de/sneak/dnswatcher/issues/93) is that DNS is never mocked, not in tests and not anywhere else.
Two documents still invite it: `TESTING.md` says "the mock constructor exists for unit-testing other packages that consume the resolver", and the comment on `NewFromLoggerWithClient` in `internal/resolver/resolver.go` calls it "useful for testing with mock DNS responses".
## What to do
- Remove `mockResolver`. Watcher tests that need DNS use the real resolver against live nameservers, with the robustness the resolver tests already use (retries, several nameservers, timeouts).
- Behaviour that depends on records changing (record changes, nameserver changes, inconsistencies) is tested by preparing the saved state the watcher starts from and letting live DNS supply the current records, or by testing the comparison on record data directly with no lookup involved. Never through a stand-in resolver.
- Stand-ins for the notifier, port checker and TLS checker may stay: they are not DNS.
- Remove the `TESTING.md` exception so it matches the README. Correct the `resolver.go` comment; if `NewFromLoggerWithClient` has no use other than mocks, remove it.
## Definition of done
- No mock, fake or stub of DNS resolution anywhere in the tree, tests included.
- Watcher tests still cover first-run baseline, record change, nameserver change, nameserver failure and recovery, port state change and TLS expiry warnings.
- `TESTING.md`, the README and code comments state the same rule.
- The suite stays within its time limit; `make check` green.
Model: opus-5-5
mockResolver in internal/watcher/watcher_test.go, used by every watcher test.
timeoutClient in internal/resolver/resolver_test.go, a stand-in DNS client that always returns a timeout, passed in through NewFromLoggerWithClient. It is covered by this issue too.
The TESTING.md exception and the comment on NewFromLoggerWithClient.
Approach:
Watcher tests use the real resolver (resolver.NewFromLogger) on targets in stable, well-run zones; the current records come from live DNS. Where a test needs something to have changed, it prepares the saved state the watcher starts from with values real DNS never returns (documentation addresses such as 192.0.2.1, nameserver names under .invalid), so the check sees a real difference: record change, nameserver change, nameserver disappearance. For recovery, the saved state marks a real nameserver as failed. Assert on notification titles and on the saved state, never on specific live record values.
The timeout test queries a real address that never answers (192.0.2.1) through the normal client with a short deadline, and checks for the timeout status.
Live-DNS robustness for the watcher tests follows what the resolver tests already do (a limit on concurrent queries, retries on transport failures). Reuse or move that code; do not build a second version of it.
If NewFromLoggerWithClient has no caller left, remove it.
The notifier, port checker and TLS checker stand-ins stay; they are not DNS.
The suite stays within its 60-second cap (sneak's ruling on #93).
Disclosure for the PR: the watcher tests now need network access, as the resolver tests already do.
Model: opus-5-5
Plan. What is on `next` today:
- `mockResolver` in `internal/watcher/watcher_test.go`, used by every watcher test.
- `timeoutClient` in `internal/resolver/resolver_test.go`, a stand-in DNS client that always returns a timeout, passed in through `NewFromLoggerWithClient`. It is covered by this issue too.
- The `TESTING.md` exception and the comment on `NewFromLoggerWithClient`.
Approach:
1. Watcher tests use the real resolver (`resolver.NewFromLogger`) on targets in stable, well-run zones; the current records come from live DNS. Where a test needs something to have changed, it prepares the saved state the watcher starts from with values real DNS never returns (documentation addresses such as `192.0.2.1`, nameserver names under `.invalid`), so the check sees a real difference: record change, nameserver change, nameserver disappearance. For recovery, the saved state marks a real nameserver as failed. Assert on notification titles and on the saved state, never on specific live record values.
2. The timeout test queries a real address that never answers (`192.0.2.1`) through the normal client with a short deadline, and checks for the timeout status.
3. Live-DNS robustness for the watcher tests follows what the resolver tests already do (a limit on concurrent queries, retries on transport failures). Reuse or move that code; do not build a second version of it.
4. If `NewFromLoggerWithClient` has no caller left, remove it.
5. The notifier, port checker and TLS checker stand-ins stay; they are not DNS.
6. The suite stays within its 60-second cap (sneak's ruling on https://git.eeqj.de/sneak/dnswatcher/issues/93).
Disclosure for the PR: the watcher tests now need network access, as the resolver tests already do.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
internal/watcher/watcher_test.godefinesmockResolver, a stand-in for theDNSResolverinterface that returns canned records, and every watcher test runs against it. That is a stubbed lookup. sneak's rule (2026-08-07, recorded on #94 and in the README section "No DNS mocking. Ever.", restated on #93) is that DNS is never mocked, not in tests and not anywhere else.Two documents still invite it:
TESTING.mdsays "the mock constructor exists for unit-testing other packages that consume the resolver", and the comment onNewFromLoggerWithClientininternal/resolver/resolver.gocalls it "useful for testing with mock DNS responses".What to do
mockResolver. Watcher tests that need DNS use the real resolver against live nameservers, with the robustness the resolver tests already use (retries, several nameservers, timeouts).TESTING.mdexception so it matches the README. Correct theresolver.gocomment; ifNewFromLoggerWithClienthas no use other than mocks, remove it.Definition of done
TESTING.md, the README and code comments state the same rule.make checkgreen.Model: opus-5-5
Plan. What is on
nexttoday:mockResolverininternal/watcher/watcher_test.go, used by every watcher test.timeoutClientininternal/resolver/resolver_test.go, a stand-in DNS client that always returns a timeout, passed in throughNewFromLoggerWithClient. It is covered by this issue too.TESTING.mdexception and the comment onNewFromLoggerWithClient.Approach:
resolver.NewFromLogger) on targets in stable, well-run zones; the current records come from live DNS. Where a test needs something to have changed, it prepares the saved state the watcher starts from with values real DNS never returns (documentation addresses such as192.0.2.1, nameserver names under.invalid), so the check sees a real difference: record change, nameserver change, nameserver disappearance. For recovery, the saved state marks a real nameserver as failed. Assert on notification titles and on the saved state, never on specific live record values.192.0.2.1) through the normal client with a short deadline, and checks for the timeout status.NewFromLoggerWithClienthas no caller left, remove it.Disclosure for the PR: the watcher tests now need network access, as the resolver tests already do.
Model: opus-5-5