Why is substituting at an interface your team owns stronger evidence than substituting at a vendor's own type?
answer
- Whose beliefs does the double encode?
- The seam decides what a pass proves
- Vendor type versus a narrow owned interface
- Assumptions collapse into one adapter
- Test that adapter against the real thing
basics
~20 sA stand-in for a vendor type encodes your beliefs about that vendor, so a pass confirms those beliefs, not real behaviour. An interface you define has a contract you control and collapses vendor assumptions into one adapter you test for real.
solid answer
~50 sBoth seams give a fast offline test, but they prove different things. Replacing the vendor's own client type means the double is a model of the vendor written from your understanding of it: retry rules, error shapes, pagination, caching, whether a missing record raises or returns empty. The test then verifies your code against that model, and nothing in the suite can tell you the model is wrong. A vendor upgrade changes the real behaviour while the double stays exactly as written, so the suite stays green. An interface you define is narrow, stated in domain vocabulary, and its contract is yours, so a stand-in can honour it completely. Every uncertain assumption about the vendor collapses into one adapter, small enough to test against a real or sandbox instance. The exception is a vendor that ships and maintains its own double.
code
pseudocode · 16 lines# Vendor seam: the double is a model of somebody else's client
client = standIn(VendorDocumentClient)
client.query(QueryBuilder().collection("rules").where("year", 2025).pageSize(50))
returns PagedResult(items = [rawRuleDoc], nextPageToken = null)
wizard = FilingWizard(RulesAdapter(client))
# Owned seam: the double honours a contract this team wrote
snapshots = standIn(RulesSnapshotSource)
snapshots.forTaxYear(2025) returns RuleSet(bracket(0, 12_500, 0.0))
wizard = FilingWizard(snapshots)
# The vendor's real semantics are pinned in exactly one place
adapterTest "republished rules are not served from the client cache":
source = RulesAdapter(realVendorClient(sandbox))
publishRules(year = 2025, revision = 7)
assert source.forTaxYear(2025).revision == 7go deeper
Know the heuristic and what it protects: prefer to substitute an interface your own team defined, because a stand-in for someone else's type only reproduces what you assumed about it. Be able to point at the adapter as the place the real dependency belongs.
Explain the mechanics: surface size, semantics you may have modelled wrong, and the fact that a vendor upgrade leaves the double untouched. Describe the split into many fast tests at the owned seam plus a few adapter tests against the real thing.
Show that you can spot a pass-through interface that buys nothing, and name the exceptions where substituting a vendor type is fine, including a vendor-maintained double. Talk about how the adapter tests are kept affordable and who owns them.
Own it as a policy question across many dependencies: which ones get an adapter, who maintains it, how the small set of real-dependency tests is budgeted, and when wrapping is not worth the maintenance it creates.
### Two candidate seams for the same dependency When code depends on something a third party supplies, a test has two places it can cut. It can replace the **vendor's own type** — the client class the vendor ships, with its methods, builders and response objects — or it can replace **a narrow interface the team defined**, which one adapter class implements by calling the vendor. Both produce a test that runs fast and offline. They do not produce the same evidence, and the difference is the whole point of the question. ### Why the vendor seam produces weak evidence A stand-in for a vendor type is a **model of the vendor written from your team's understanding of it**. When the test then passes, what it has confirmed is that your code behaves correctly *against your beliefs about the vendor*. The vendor was never present, and nothing in the suite can tell you those beliefs are wrong. Four specific failure modes follow: - **Semantics you modelled incorrectly.** Retry behaviour, pagination, whether a missing record raises an error or returns an empty result, whether a returned collection is a live view, whether a client caches. Your stand-in returns whatever you assumed. Production returns what the vendor actually does. - **Surface size.** A vendor client typically exposes far more than your code uses, often behind builders and wrapper types. Replacing it means constructing a lot of vendor-shaped scaffolding in every test, which is expensive to write and expensive to change. - **Silent drift on upgrade.** When you take a new version of the vendor library, the behaviour it ships may change while your stand-in stays exactly as written. The suite goes green precisely because nothing about the real dependency was involved. - **Vendor vocabulary leaks into your tests.** Your test file now talks in the vendor's nouns, so swapping vendors means rewriting tests that had nothing to do with the vendor's identity. ### Why an owned interface produces stronger evidence An interface the team defines is narrow — usually a handful of methods stated in the domain's own words — and **you define its contract**, so a stand-in can honour that contract completely. There is no gap between what the interface promises and what the double does, because you wrote both. More importantly, this seam concentrates every uncertain assumption about the vendor into **one adapter**. That adapter is now the only code in the system whose correctness depends on the vendor's real behaviour, and it is small enough that you can afford to test it against a real or sandbox instance of the dependency. So the suite splits cleanly: a large body of fast tests that use the owned seam and prove your logic, plus a small body of slow tests that prove the one place where your beliefs about the vendor are encoded. The evidence adds up; with the vendor seam it does not, because no test ever meets the vendor at all. ### The worked case, and the defect the seam decided In the **tax-filing wizard**, the rules-snapshot dependency comes from a vendor library with a paged document query and — as it turned out — a client-side cache with its own eviction policy. The team defined `RulesSnapshotSource.forTaxYear(year) -> RuleSet`: one method, domain vocabulary, no vendor types in the signature. Unit tests substitute at that method. One adapter calls the vendor, and six adapter tests run against a real sandbox instance in roughly 28 seconds per commit. Those six tests are where a **stale-cache read** surfaced: the vendor client returned a snapshot from an earlier tax year after the rules were republished. No stand-in for the vendor type would have caught it, because nobody modelling that client had any reason to model its cache. That is the general shape — the defects that survive a doubled suite are exactly the ones about semantics you did not know to imitate. ### When the vendor seam is defensible The heuristic often quoted as "don't mock what you don't own" is a default, not a law, and a strong candidate names the exceptions: - The vendor **ships and maintains its own test double or fake**. Then the model of the vendor is the vendor's, kept in step with each release, and the objection largely disappears. - The type is a **pure data holder** with no behaviour worth modelling. - **Legacy code with no seam**, where introducing one is the larger project. Substituting at the vendor type is then a stepping stone you record as debt, not a destination. ### The trap to name An owned interface that mirrors the vendor's shape method-for-method buys nothing: the stand-in must model the same semantics, so you have the vendor seam plus an extra file. The test is whether the interface would survive replacing the vendor entirely. If its method names, parameters and return types are stated in your domain's terms and would not change, the seam is real. If they are the vendor's names with a different package, it is not.
- When is substituting directly at a third-party type still the right call?Three cases. When the vendor ships and maintains its own test double, the model of the vendor is the vendor's and stays in step with each release. When the type is a pure data holder with no behaviour worth imitating. And in legacy code with no seam, where introducing one is the larger project, substituting at the vendor type is a stepping stone you record as debt rather than a destination. Outside those, the default holds.
- How can you tell that an interface your team wrote has become a pass-through of the vendor's shape?Look at whether it would survive replacing the vendor. If the method names, parameters and return types are stated in your domain's vocabulary and would not change, the seam is real. If they mirror the vendor's methods and carry the vendor's response objects across the boundary, the double still has to model the vendor's semantics, so you have the vendor seam plus one extra file to maintain.
- The team upgrades the vendor library and the whole suite stays green. Why is that not reassuring?If every test substitutes the vendor's type, no test met the new version at all, so green is exactly what you would expect whether or not the behaviour changed. Reassurance has to come from the small set of adapter tests that run against a real or sandbox instance, which is the only place the upgrade is actually exercised. A suite that cannot go red on a vendor change is silent about vendor changes.
saying these in an interview costs you the question
- Doubles the vendor's client type and calls the code well isolated
- Treats a green suite as proof the vendor integration works
- Never tests the adapter against a real or sandbox instance
- Re-models vendor retry and error semantics in every test file
- Writes an owned interface that mirrors the vendor's methods exactly