Test without launching a browser¶
This guide shows how to use WithOpener to inject a fake opener in tests, so
your test suite never spawns a real browser or mail client — and how to assert
on the URL that would have been opened.
The problem¶
By default, OpenURL delegates to the OS URL handler. In a test, that would
pop a real browser window (or fail on a headless CI machine). You want to
exercise your code's URL-opening path and check what it produced, without any
side effect on the machine running the tests.
Inject a fake with WithOpener¶
OpenURL accepts options, and WithOpener replaces the default OS opener with
any func(rawURL string) error you supply. Point it at a closure that records
the URL instead of opening it:
func TestOpensDocsURL(t *testing.T) {
var opened string
err := browser.OpenURL(context.Background(), "https://example.com/docs",
browser.WithOpener(func(rawURL string) error {
opened = rawURL
return nil
}),
)
if err != nil {
t.Fatalf("OpenURL returned an error: %v", err)
}
if opened != "https://example.com/docs" {
t.Errorf("opened %q, want the docs URL", opened)
}
}
The fake receives the URL only after validation has passed, so a captured value confirms the URL cleared the scheme allowlist, the length bound, and the control-character check.
Validation still runs — the fake is never called on a reject¶
WithOpener does not bypass the gate. If the URL is rejected, OpenURL returns
the sentinel error and your opener is never invoked — so you can assert that a
bad URL never reached the "browser":
func TestRejectsDisallowedScheme(t *testing.T) {
called := false
err := browser.OpenURL(context.Background(), "javascript:alert(1)",
browser.WithOpener(func(string) error {
called = true
return nil
}),
)
if !errors.Is(err, browser.ErrDisallowedScheme) {
t.Fatalf("want ErrDisallowedScheme, got %v", err)
}
if called {
t.Error("opener was called for a disallowed scheme")
}
}
Simulate an OS-opener failure¶
Return an error from the fake to exercise your code's handling of a failed open
(for example, a headless host with no browser). OpenURL wraps it, so match on
your own error, not on a sentinel:
sentinel := errors.New("no browser available")
err := browser.OpenURL(context.Background(), "https://example.com",
browser.WithOpener(func(string) error {
return sentinel
}),
)
if !errors.Is(err, sentinel) {
t.Fatalf("want the opener error to propagate, got %v", err)
}
Notes¶
- Passing
niltoWithOpeneris treated as "use the default" — it does not disable opening. Always pass a real closure in tests. - The fake is per-call: supply
WithOpeneron eachOpenURLyou want to intercept. There is no global opener to set or reset, which keeps parallel tests (t.Parallel()) free of shared mutable state.
See also¶
- Open URLs safely — the production-side discipline.
- Threat model & the validation gate — why the opener is a seam in the first place.