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.