rubocop-hgoostdd
Custom RuboCop cops that codify the rules of Hardened GOOS TDD — the testing discipline that wraps Freeman & Pryce's outside-in TDD with explicit guardrails against the failure modes that bite long-lived mockist codebases: interface drift, behavior drift, hidden globals, and ossified mocks.
What it enforces
| Cop | Rule | Autocorrect |
|---|---|---|
HGOOSTDD/NoTimeInDomain |
§6 — no Time.now / Time.current in domain code |
→ @clock.now (unsafe) |
HGOOSTDD/NoSecureRandomInDomain |
§6 — no SecureRandom.* in domain code |
→ @id_gen.<method>(args) (unsafe) |
HGOOSTDD/NoRailsLoggerInDomain |
§6 — no Rails.logger in domain code |
→ @logger preserving chain (unsafe) |
HGOOSTDD/NoEnvOutsideAppConfig |
§6 — ENV[ / ENV.fetch only in app/support/app_config.rb |
— |
HGOOSTDD/NoTimecopInDomainSpecs |
§6 — no Timecop / travel_to in inner-loop specs |
— |
HGOOSTDD/NoMocksInAcceptance |
§2 — no instance_double / allow / expect…to receive in spec/acceptance/** |
— |
The three autocorrectable cops are marked SafeAutoCorrect: false —
the rewrite assumes the surrounding object has the matching ivar wired
by an injected collaborator. rubocop -a skips them; only rubocop -A
applies.
Install
# Gemfile
group :development, :test do
gem "rubocop-hgoostdd", require: false
end
# .rubocop.yml
require:
- rubocop-hgoostdd
AllCops:
TargetRubyVersion: 4.0
NewCops: disable
DisabledByDefault: true
SuggestExtensions: false
DisabledByDefault: true is recommended unless you're also running
stock RuboCop — otherwise default style cops will fire and bury the
HGOOSTDD output.
Default paths
Each cop ships with Rails-app-shaped Include/Exclude defaults under
config/default.yml. Override in your own .rubocop.yml if your
project structure differs:
HGOOSTDD/NoTimeInDomain:
Include:
- 'lib/**/*'
Exclude:
- 'lib/clock.rb'
The six rules in brief
- §1 Only mock types you own. Wrap third-party SDKs in adapters; mock the adapter, not the SDK.
- §2 Outer-loop acceptance test against real Rails; inner loop mockist and fast.
- §3 Contract tests for adapters — paired against real and fake.
- §4 Inject collaborators; no hidden reaches.
- §5 Real > Fake > Stub > Mock. Fakes are first-class.
- §6 No implicit globals in domain code.
Full discipline at the goos_tdd worked example (the sandbox that produced this gem).
Origin
These cops were extracted from a Rails sandbox built to validate the
discipline end-to-end (goos_tdd). Each cop has a paired
expect_offense / expect_no_offenses spec; the three autocorrect
cops also assert via expect_correction. 24 examples, 0 failures.
License
MIT.