Skip to content

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 nil to WithOpener is treated as "use the default" — it does not disable opening. Always pass a real closure in tests.
  • The fake is per-call: supply WithOpener on each OpenURL you 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