A Green Local Test Can Hide a Broken Project Graph
“The tests pass on my machine” is often treated as a testing problem. Sometimes it is really a dependency-graph problem. Consider a browser application that contains a small deterministic rule: given one origin, derive another related origin. The rule has no UI state, network access, storage, or framework lifecycle. It deserves focused unit tests. The quickest route appears to be adding a project…
A common testing approach is to assume tests always succeed on your local machine, but this can mask deeper issues. Consider a browser app with a rule that derives an origin from another, which has no UI or external dependencies. It seems suitable for unit testing, and adding a project reference from the test project seems like the simplest solution. The tests pass locally, and the change appears complete. However, CI runs using different SDK properties may fail before test discovery.
The failure isn't in the rule itself but in the way the test project imports the application head and associated build behavior. A project reference does more than just share types; it also brings build graphs, targets, workloads, generated assets, and property-sensitive behavior. When a test project references a browser head, it participates in the build pipeline, and if CI disables web-asset work, the test project may not compile correctly. This local green run and CI failure are compatible, as they exercise different build contracts.
Rather than trying to make the pipeline accommodate the problem, it's better to understand what the test actually needs. In this case, the test only needs the deterministic mapping rule, not the application host. Moving the rule behind a neutral library boundary allows both the browser application and the test project to depend on it directly. This refactoring removes the invalid application reference and prevents future changes to the head from expanding the test project's build surface.
However, it's essential to consider the trade-offs. A neutral library shouldn't accumulate unrelated helpers simply because tests can reach them. The placement should be narrower, with the rule sitting beside the abstraction used by its only application consumer in a library already shared by both relevant projects. The documentation should explain why the more natural location is currently invalid and what future conditions might trigger a move.
To reproduce the build contract that failed, run more than just the convenient local command. Confirm that the test project no longer references the executable head and that both consumers already reference the neutral library. Run the focused behavior tests and build or test with the same SDK properties CI supplies. This approach ensures the build contract failure is adequately addressed and avoids creating a dumping ground for unrelated helpers.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.