A Green Build Is Not a Route Test

2026年9月1日2 次浏览来源:Dev.to阅读原文

A Blazor Hybrid application can compile successfully and still fail as soon as its router starts.

The uncomfortable part is that nothing needs to be wrong with the individual components.

The failure can live in the composition of otherwise valid Razor pages: two scanned assemblies declare the same route, and the conflict is only discovered at runtime.

That makes route ownership an architectural contract, not just an detail.

The invisible route set Multi-head applications often share UI through a Razor class library.

A web host, a progressive web app, and a .NET MAUI host may each scan two places: the host's own assembly; and a shared UI assembly.

This is useful.

Shared pages can be implemented once while each host keeps pages that depend on its own authentication model, platform services, or startup behaviour.

It also creates a set that the compiler does not reason about: all routes visible to one router.

Imagine the shared assembly contains: Later, a host-specific page independently declares the same path.

Both components are valid.

Both assemblies compile.

The duplicate becomes visible only when that host's router scans the combined set.

Why a green build is insufficient Compilation proves that each project is structurally valid.

It does not prove that independently compiled route tables are unique when combined.

This is a general modularity problem.

A module can be correct in isolation while the application graph is invalid after composition.

Dependency-injection cycles, duplicate HTTP endpoints, conflicting configuration keys, and route collisions all share this shape.

For Blazor, the timing makes the failure especially awkward.

The collision can wait until the router initialises on first load.

A pipeline may restore, compile, and test successfully, while the person opening the application becomes the integration test.

Why the MAUI host changes the decision Web applications can cheaply receive post-deploy smoke coverage: request a page and assert that it renders instead of returning an error.

A MAUI build is different.

Producing an Android or iOS binary does not prove that a BlazorWebView can start, compose its scanned assemblies, and navigate.

A real-device or emulator startup test is valuable, but it adds workload installation, boot time, device management, and another source of CI instability.

In the committed change I reviewed, the web-facing heads had page smoke coverage while the MAUI workflow had no equivalent startup check.

That made a fast structural guard a sensible intermediate layer.

Build a focused collision guard The guard does not need to reproduce the whole Razor runtime.

It needs to answer one architectural question: Within the assemblies scanned by each host, is any route declared more than once?

A practical implementation can: enumerate source files for a host and its shared UI project; ignore generated and trees; remove Razor and HTML comments; recognise only complete directive lines; normalise paths with an explicit case policy; and group the resulting declarations by route.

Run the same assertion for every head.

A collision report should name the generalized host and the two source locations so a developer can resolve ownership quickly.

This test is intentionally narrow.

It does not need a browser, emulator, dependency container, or application server.

Its value is that it turns a late composition failure into an early, deterministic signal.

Make the guard prove itself Source guards have a dangerous failure mode: they can pass because the parser found nothing.

That is why a negative assertion such as “there are no duplicates” is incomplete.

Add an anti-vacuous test that requires the parser to find a few routes that must exist.

If a future Razor syntax change, path change, or parser bug empties the result, the test fails instead of giving false confidence.

Test false positives too.

Comments often contain route examples: A naïve text search counts that as a declaration.

A focused parser should ignore it, and a test should pin that behaviour.

Excluding generated copies matters for the same reason; otherwise every route can appear to collide with itself.

The trade-off A source-level guard is fast and portable, but it is coupled to project layout and Razor syntax.

It does not exercise the real router, dependency injection, native lifecycle, or device packaging.

A device startup test provides higher-fidelity evidence, but it is slower and more operationally expensive.

The two tests answer different questions: source guard: is the declared route set structurally unique for every host? device smoke test: can the packaged application actually start and navigate in its platform environment?

Use the source guard for immediate feedback, then add at least one device-startup path when the release risk justifies the cost.

A practical checklist Before adding or moving a shared Razor page: Write down every assembly each host scans.

Decide whether the route is shared or host-owned.

Assert uniqueness across each host's complete scanned set.

Prove the guard finds known routes.

Prove comments and generated output do not create false routes.

Keep a device startup check on the roadmap, because structural tests are not runtime proof.

Shared UI is valuable precisely because it removes duplication.

The price is making the composition rules explicit.

A green build tells you the parts compile.

A route test tells you whether those parts can safely meet.

分享
Baike.dev

baike.dev helps you discover great languages, frameworks, databases, DevOps and cloud-native tools.

Quick links

About

Contribute

Found a great developer tool? Share it with the community.

Submit a tool
© 2026 baike.dev Developer EncyclopediaUpdated daily · Discover great developer tools