skip to content

Must a facade be a single class, be stateless, or be a singleton — and how does introducing one affect testing of client code?

level: middleimportance: nice to knowfreq 26%

answer

  1. Intent constrained, shape not
  2. Singleton is common, not required — inject instead
  3. State is fine; domain rules are not
  4. Client tests: stub one, not ten
  5. Stub honesty needs integration/contract tests

basics

~20 s

None of those are required. A facade can be several classes, hold state, and be created per use. For tests, clients now stub one facade instead of many collaborators, which makes their tests shorter — but the facade itself still needs integration tests.

solid answer

~50 s

The pattern constrains intent, not shape. A facade may be one class or several cooperating entry points; it may be an interface with an implementation (helpful for substitution) or a concrete class; it may hold state such as a connection or configuration; and it need not be a singleton — GoF mention singleton as a *common* choice, not a requirement, and injecting it is usually better for testability. Testing effects: client tests improve, because one stubbed dependency replaces the whole subsystem wiring, and the stub's small surface makes intent obvious. Two cautions. First, coverage moves rather than disappears: the orchestration the facade encapsulates is real logic and needs its own tests, typically integration-level against the real subsystem. Second, an over-simple stub can be dishonest — if the facade's real behaviour includes failure modes, retries or ordering the stub ignores, client tests go green while production breaks; contract or integration tests keep the stub honest.

go deeper

for a junior

Say that none of those constraints are required and that client tests get simpler because there is one thing to stub.

for a middle

Explain injection versus static singleton, that state is acceptable but business rules are not, and that the facade still needs its own tests.

for a senior

Add the dishonest-stub failure mode and how signatures, shipped fakes and contract tests keep doubles faithful.

for a principal

Discuss it as boundary testing strategy: owner-maintained test doubles, consumer-driven contract tests, and pointing integration effort at the concentrated risk the facade creates.

### What the pattern actually requires Facade specifies **intent** — a unified, higher-level interface over a subsystem — and **direction** — clients depend on it, the subsystem does not know it. Everything else is an implementation choice that interview folklore often turns into false rules. **Myth 1: 'A facade must be one class.'** No. A subsystem can have several facades, each aimed at a different client group (reporting, checkout, admin) — this is usually *better* than one wide facade, and follows Interface Segregation. A facade can also be one entry point that returns focused capability objects (`api.orders()`, `api.refunds()`), distributing the surface while keeping discovery in one place. **Myth 2: 'A facade must be stateless.'** No. Facades commonly hold configuration, a connection or client, caches, or a builder's accumulated defaults. Statelessness is nice when you can have it (thread-safety, easy sharing), but a database or HTTP facade that holds a pool is entirely normal. What a facade should *not* hold is **domain state and business rules** — at that point it is an application service or a domain object, and naming it a facade misleads reviewers. **Myth 3: 'A facade must be a singleton.'** GoF note that a facade is *often* a singleton because one instance usually suffices — that is an observation, not a constraint. In modern code, a singleton facade created via a static accessor is a testability problem: clients cannot substitute it, tests share mutable state, and parallel test execution becomes flaky. Prefer dependency injection with a single container-managed instance: you get 'one instance' without the static coupling. **Myth 4: 'A facade must be an interface.'** Not required. Extract an interface when you have a genuine reason — multiple implementations, a published boundary consumers stub, or a module seam you may re-implement remotely. A one-implementation interface added purely 'for mocking' is often noise in languages with capable test doubles; judge per ecosystem. ### Effects on testing **For client code — usually a clear win.** Before: a test must construct or stub every subsystem collaborator, and must reproduce the correct call ordering to make the code under test behave. That test is long, brittle, and couples the test to subsystem internals — rename a subsystem method and dozens of unrelated tests break. After: the client depends on one type. The test stubs one thing, and the stub reads as intent (`converter.convert(...) returns file`). Tests get shorter and survive subsystem refactors. **For the facade itself — the work moves, it does not vanish.** The sequencing, defaults, error handling and cleanup the facade encapsulates are real logic with real bugs. Unit-testing a facade by mocking every collaborator and asserting call order tends to produce a change-detector test: it verifies the implementation you just wrote and breaks on harmless refactors. Better to test the facade at integration level against the real (or realistically faked) subsystem, asserting observable outcomes. **The dishonest-stub trap.** A facade's stub reflects whatever the client's author *believes* it does. If the real facade can time out, retry non-idempotently, return partial results, or throw a translated error, and the stub always returns a happy value, client tests pass while production fails. Countermeasures: keep failure modes visible in the facade's signature (result types, declared errors, timeout parameters); ship a shared fake/test double alongside the facade, maintained by its owner; and use contract tests for cross-team or network facades so the double stays faithful. **Rule of thumb.** Facades make *client* tests cheaper and *system* tests more important. Do not let the drop in unit-test friction convince you the integration risk went away — the facade concentrated it in one place, which is exactly where you should point your integration tests.

  • Why is a statically-accessed singleton facade a testing problem?
    Clients reach it through a global rather than a parameter, so tests cannot substitute it, shared mutable state leaks between tests, and parallel runs turn flaky. Injecting a single container-managed instance gives you the same 'one instance' property while keeping the dependency explicit and replaceable.
  • How would you test the facade itself without writing a change-detector test?
    Test it at integration level against the real or a realistic fake subsystem and assert observable outcomes rather than call sequences. Mock-heavy tests that assert 'called A then B then C' re-state the implementation and break on refactors that preserve behaviour.
  • What can a facade's owner do to stop consumers writing unrealistic stubs?
    Ship a maintained fake alongside it, keep failure modes visible in the signature (declared errors, result types, timeouts) rather than hidden, and use contract tests so the double is verified against the real implementation.

saying these in an interview costs you the question

  • Stating that facades must be singletons — GoF describe it as a common choice, and static singletons hurt testability.
  • Claiming a facade must be stateless; holding a connection, client or configuration is ordinary.
  • Assuming there can be only one facade per subsystem; several narrow, client-specific facades are usually better.
  • Believing that stubbing the facade removes the need to test the orchestration it encapsulates.
  • Unit-testing a facade purely by asserting the order of mocked calls, producing a test that only detects refactors.

context