Contents

You write gotest suites, open VS Code’s Testing sidebar — and it’s empty. No test tree, no CodeLens Run buttons above your suite methods, nothing to debug. Standard Go tooling has nothing to show you, because as far as gopls and the Go Test Explorer are concerned, your suites aren’t tests at all.

The gotest VS Code extension exists to close this gap. Because it’s purpose-built for suite-based testing rather than a generic test runner, it can surface things standard tooling can’t: the suite hierarchy, the behavioral spec view, and coverage that survives across runs.

Installation is one command — code --install-extension mvrahden.gotest — or grab it from the VS Code Marketplace or Open VSX. The extension requires gotest v1.27.0 or newer and validates the CLI version on activation. The rest of this post is the feature tour.

Why your editor can’t see gotest suites

To understand why the extension matters, you need to understand what the editor sees without it.

gotest generates standard go test code from your suite definitions and injects it via Go’s overlay filesystem. The generated files never touch your module — they live in a temporary directory, invisible to your source tree and to git status. This is a deliberate architectural choice: your module stays clean, the generated code can’t go stale, and the overlay handles invalidation automatically.

But it comes with a trade-off. Standard Go tooling — gopls, the Go Test Explorer, go test itself — can only see what’s in your module. Since the generated test functions aren’t there, your editor doesn’t know your suites are tests. It sees structs with methods. Not test suites. Not test cases. Just ordinary Go code.

Here’s a gotest suite:

cart_test.go
type CartTestSuite struct {
    cart *Cart
}

func (s *CartTestSuite) BeforeEach(t *gotest.T) {
    s.cart = NewCart()
}

func (s *CartTestSuite) TestAddItem(t *gotest.T) {
    t.When("the cart is empty", func(t *gotest.T) {
        s.cart.Add("widget", 1)
        t.It("adds the item", func(t *gotest.T) {
            gotest.Equal(t, 1, s.cart.Len())
        })
    })
}

To gopls, this is a struct called CartTestSuite with two methods. It’s not a test. There is no func TestXxx(t *testing.T) in the file or anywhere in the module. The standard Go Test Explorer shows nothing. CodeLens doesn’t offer a “Run Test” button. The test sidebar is empty.

When you run gotest ./... from the terminal, the CLI generates the wiring code into a temporary directory, passes it to go test via the -overlay flag, and everything works. But the editor never sees that overlay. It only sees your module — and your module has no test functions.

This is the gap the extension closes.

Test discovery

The extension’s first job is the most fundamental: making your suites visible as tests.

On activation, it runs the gotest discover subcommand, which loads your test packages, identifies suite types (structs ending in TestSuite) and their test methods (methods starting with Test), and reports them as structured JSON — the same discovery the test runner itself uses, so the editor and the CLI can never disagree about what counts as a suite. The extension registers these with VS Code’s Test Controller, and they appear in the Testing sidebar as a structured tree: Package > Suite > Method, with a fourth level — Subtests — populated after the first execution.

This is four levels of hierarchy. The standard Go Test Explorer, even in projects that use stdlib tests, has no structural level between the package and the test function — there is no suite level. And for testify/suite projects, it effectively shows a single entry — the func TestRunSuite(t *testing.T) entry point that wraps the entire suite. Individual methods are invisible until a run happens to reach them.

Discovery re-runs automatically when _test.go files change. The tree stays in sync with your code without manual refresh.

Run and debug at any level

Once suites are discovered, CodeLens buttons appear inline above every suite type and test method in your _test.go files: Run and Debug. Click to execute immediately.

Package-level and file-level actions appear at the top of the file. When a file contains multiple suites, a “Run File” action runs just the suites in that file.

You can also run from the Testing sidebar: click a suite to run all its methods, click a method to run just that one. Multi-select is fully supported — pick three methods across two suites and run them in one action.

After the first execution, subtests appear as children under their method node — the When and It blocks that gotest maps to t.Run calls. These are navigable and individually re-runnable. Select a specific When block and run just that context with one click; the extension builds the correct -run regex to target it.

Debugging uses Delve. The extension generates the overlay, prepares the test binary with debug flags, and launches a debug session with the right test filter. Set breakpoints in your suite methods and step through them like any other Go code.

The Spec View

This is where the extension goes beyond what standard test tooling does for any framework.

After each test run, the Spec View panel renders your test results as a behavioral specification — a hierarchical tree showing suites, methods, and their When/It blocks with pass/fail/skip indicators:

Spec View
Cart
  AddItem
    the cart is empty
       adds the item
       sets quantity to 1
    the item already exists
       increments the quantity
  RemoveItem
    the item exists
       removes it from the cart
    the item does not exist
       returns ErrNotFound

The Spec View is an interactive panel, not static text. It supports:

The Spec View is what connects the editor experience to the tests-as-documentation philosophy. Your tests define what the system does. The Spec View renders that definition as a living document, always in sync with the code.

Coverage

The extension integrates with VS Code’s native coverage API. Run tests with the Coverage profile in Test Explorer, and statement-level coverage gutters appear inline in your source files — green for covered, red for uncovered, with execution counts per statement.

Three things make this more useful than running coverage from the terminal:

Watch mode

Start continuous testing with the Start Watch command. The extension spawns a gotest watch process that monitors file changes and re-runs affected tests. Results stream into Test Explorer and Spec View in real-time.

A status bar item shows the active watcher count. Click it to stop all watchers. If the watch process crashes, it auto-restarts with exponential backoff.

Combined with the Spec View, this creates a tight feedback loop: save a file, watch re-runs the affected tests, the Spec View updates with pass/fail status, coverage gutters refresh. You see the behavioral impact of every change without leaving the editor or typing a command.

Structured exports for AI-assisted workflows

The extension’s export capabilities — spec output, test results, and coverage summaries — produce structured data that feeds directly into AI-assisted development workflows.

Copy the Spec View output and paste it into an AI conversation. The behavioral specification tells the model what the system does at the level stakeholders care about: “UserService / Create / email is valid / creates the user.” That’s more useful context than raw source code for understanding system behavior.

Copy test results with status filtering — failures only, for instance — and paste them alongside the relevant source. The AI sees exactly which behaviors broke and what the assertion messages said, without wading through passing tests.

Copy the coverage summary and paste it into a conversation about test coverage gaps. The tabular format shows exactly which functions are covered and which aren’t.

These aren’t AI-specific features. They’re structured export features that happen to compose well with language models because the data is behavioral, not mechanical. The tests describe what the system promises. The exports make those descriptions portable.

Scaffold generation

Reducing the friction of creating new suites matters, especially for teams adopting gotest incrementally. The extension offers scaffold generation through multiple entry points:

The generated file opens automatically, and discovery refreshes to include the new suite immediately.

The trade-off, revisited

gotest’s code generation architecture is a deliberate choice. It enables compile-time validation of lifecycle hooks, overlay-based injection that keeps your module clean, process-level suite isolation, and zero reflection overhead. The Code Generation vs Reflection post explains why these properties matter.

The cost is that standard Go tooling doesn’t see your suites. This is a real cost. If you open a gotest project in VS Code without the extension, your suites are invisible to the Testing sidebar, CodeLens doesn’t appear, and you’re running everything from the terminal.

The extension makes that cost disappear — and then goes further. Suite-aware discovery, behavioral Spec View, persistent coverage, watch integration, structured exports, scaffold generation. These aren’t features that standard Go test tooling provides for stdlib tests either. They’re possible because the extension is purpose-built for a framework whose structure is rich enough to support them.

That’s the architectural story: gotest’s code generation creates a gap between your source code and the Go toolchain. The extension bridges that gap — and because it understands suite structure natively, it builds a richer bridge than a generic test runner ever could. If you prefer the same feedback loop in the terminal, The Inner Loop covers gotest watch and --spec from the command line; and Go Tests as Living Documentation shows what to do with the spec output once you have it.

Install the gotest VS Code extension.

code --install-extension mvrahden.gotest