# neotest: a framework where the adapter table is the real documentation

> neotest does not run tests, it locates them, reports status signs and offers an API, so almost everything you actually read about it lives in the adapters. Here is what the repository itself makes that contract clear about.

**nvim-neotest/neotest** — An extensible framework for interacting with tests within NeoVim.

- Repository: https://github.com/nvim-neotest/neotest
- Stars: 3,117 · Forks: 187
- Language: Lua
- License: MIT
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/nvim-neotest-neotest

## A framework with an empty middle

The repository description is one line: an extensible framework for interacting with tests within NeoVim. Read that carefully, because the framework does not know what pytest is. It knows how to find a test position, how to run a command and parse its output into a structured result, how to attach a status sign to a line, and how to expose that to other plugins through an API.

The language knowledge and the runner invocation live in adapters, which are separate repositories. The README says it plainly: to get started you will also need to install an adapter for your test runner, and to see the adapter's documentation for their specific setup instructions. That sentence is the whole design in miniature. The core is the transport, the protocol and the rendering; everything you would think of as the interesting part is somebody else's project.

There is one dependency. Neotest uses nvim-nio, and it is a hard requirement rather than an optional extra, because the framework is built around asynchronous tasks that have to be cancellable and schedulable. Most adapters will also require a treesitter parser installed for the related language, which means a working neotest setup usually pulls in several parsers before a single test runs.

A second dependency is subtler. The README notes that neotest uses the `CursorHold` event, and that this event uses the `updatetime` setting, which is by default very high. A high `updatetime` is correct for normal editing, since it controls how often the editor writes its swap file and does other background work. But CursorHold fires on that same timer, so a configuration that makes status signs update responsively also makes the editor write to disk constantly. The project is explicit that lowering it can lead to excessive writes to disk.

## The updatetime conflict and the plugin that works around it

This is a design problem rather than a bug, and the README treats it as such. Neotest wants a fast feedback loop for gutter signs; Neovim's timer for background work wants to be slow. The straightforward fix, lowering `updatetime`, has a side effect that shows up as wear on your disk rather than as an error message.

The recommended solution is FixCursorHold.nvim, which allows detaching `updatetime` from the frequency of the CursorHold event. The mechanism is straightforward: keep the editor's own timer at its safe value and have the plugin generate the CursorHold event on its own schedule, so signs refresh often without the editor doing the same amount of work often.

What makes this paragraph worth reading is the hedge in it. The README says the repo claims it is no longer needed but it is still recommended, and it links to an issue on that repository to explain why. In other words, the workaround's author has suggested it is obsolete, the neotest maintainers have not accepted that, and the disagreement is documented in public rather than resolved. Practically, the guidance is to install it anyway, and the install snippets reflect that: the dependency appears in every example.

The install path also has a hint about intent. The README says to install with your favourite package manager alongside nvim-dap, which tells you the expected end state is a debugging and testing setup sharing one workflow rather than two separate plugin ecosystems. Here is the lazy.nvim form, which is also the shortest of the four:

```lua
{
  "nvim-neotest/neotest",
  dependencies = {
    "nvim-neotest/nvim-nio",
    "antoinemadec/FixCursorHold.nvim",
  }
}
```

The README gives equivalent blocks for dein, vim-plug and packer.nvim, so the choice of plugin manager is not a barrier. A LuaRocks badge also sits in the installation section, which suggests the module is published there as well as through Neovim package managers.

## What the runner table tells you about the project

The longest part of the README is a table mapping test runners to adapters, and it is more informative than a feature list would be. Around forty runners appear, spanning Python, JavaScript, Go, Ruby, PHP, Rust, Elixir, .NET, Java, Kotlin, Haskell, Dart, Scala and more. For several languages there are two or three competing adapters.

Where there are duplicates, the names usually carry the implementation detail. Go has neotest-go and neotest-golang. .NET is split into neotest-dotnet for treesitter and neotest-vstest for the Microsoft test runner. Rust appears twice, once as neotest-rust for treesitter and once pointing at rustaceanvim for LSP-based navigation, which is an interesting exception: the second entry is not a neotest adapter at all but an existing plugin that integrates with it.

That last case is worth dwelling on, because it shows the framework is not trying to own everything. Rust support already existed through the LSP side of rustaceanvim, so rather than duplicate it, the table points there. The same pattern holds for Playwright, where a browser automation runner is treated as just another runner in the table.

Several adapters are marked as treesitter based in the runner column, which tells you the parsing strategy for locating test functions is not uniform across the ecosystem. Some adapters clearly parse test definitions out of source with treesitter, and some drive the framework's own introspection or the runner's machine-readable output instead. The table does not explain which is which, and that is a real limit of what the README offers: you can tell which runners are supported, but not how any particular adapter determines what a test is.

The practical consequence is that choosing an adapter is a small research task. The table narrows the field to one or two candidates, and from there the adapter's own README has to answer the questions the core README leaves open.

## Release cadence and the early stage warning

The README opens with a sentence that deserves to be quoted rather than paraphrased: this is early stage software. It appears above the table of contents, before any of the installation instructions, so the project states its own uncertainty before asking for your time.

The metadata around the repository is more reassuring than that warning, but it needs reading precisely. The repository is not archived, and the last push was on 2026-08-16, which is recent. It has around 3,100 stars and 186 forks, with 104 open issues. A project with that many open issues is either popular or under-maintained, and the release history suggests active work on the core rather than on individual language adapters.

Three releases are worth naming because they describe what the maintainers think matters. Version 5.19.1 on 2026-07-15 removed the tree-sitter dependency from the subprocess layer entirely. Version 5.19.2, published the same day, fixed two client-side problems, checking that a buffer is not a directory and handling a nil stat result. Version 5.20.0 on 2026-07-24 added support for a `match_limit` in parse options.

That sequence is a fair summary of the project's centre of gravity. The tree-sitter removal reduces a heavy dependency and loosens the coupling between the framework and the parsing strategy, which is the same direction the adapter table points. The two client fixes are the kind of bug you only find when many people are driving the API in unusual ways, and the `match_limit` option is a performance valve for large files. In other words, the core is being hardened and tuned; the adapters are where breadth lives, and they move on their own schedules.

The repository structure is small and tidy for a Neovim plugin: `lua/` for the source, `plugin/` for startup registration, `doc/` for the help files, `tests/` for its own tests, `scripts/` for tooling, and a `stylua.toml` that says formatting is enforced. There is also a `LICENCE.md`, British spelling included, holding the MIT terms that the metadata reports.

## Where the README stops and the help files begin

The README's table of contents lists Introduction, Installation, Supported Runners, Configuration, Usage, Consumers, Strategies, Writing Adapters and two subsections on parsing tests in a directory and collecting results. The visible body then does something revealing: the Introduction section is a single sentence pointing at `:h neotest` for details on how neotest is designed and how to interact with it programmatically.

So the framework delegates its own design documentation to its Vim help files. For a Neovim plugin that is a defensible choice, since `:h` is searchable, offline and versioned with the code. But it does mean the README is best read as an entry point and a compatibility table, not as a manual.

The Consumers heading is where that distinction starts to matter, because it names the plugins you are likely to install next. Output window, summary window, diagnostic messages and status signs are listed as consumer-facing features, which tells you that the core produces data and separate plugins decide how to display it. If you have seen a test summary panel in someone's Neovim config and assumed neotest drew it, the section headings suggest otherwise.

Strategies and Writing Adapters are the sections that would matter most to someone building an adapter for an unsupported runner, and they are precisely the ones a reader cannot get from the README alone. The names are informative: a strategy is how a position maps to a command, and writing an adapter means parsing tests in a directory and then collecting results. But the body text is not in the README you are reading, so treat any confident description of the adapter API as something to check in `:h neotest` rather than as documented fact here.

This is a fair shape for the project. A framework whose interesting surface is its extension points needs long-form documentation more than it needs a long landing page, and Neovim's help system is the established place for that. It also explains the persistent advice to read `:h neotest` before configuring anything.

## Conclusion

neotest is a good fit if you use Neovim as a serious development environment and want test status in your gutter without giving up the terminal. It is a bad fit if you want a plugin that understands your framework on its own, because the framework knowledge is not in this repository at all, it is in a separate adapter you have to find, install and configure. Read the runner table before anything else, because it is the only map of what is actually supported, and install FixCursorHold.nvim even though the README says it may no longer be needed, since the cost of a stale status sign is higher than the cost of one more plugin. The docs to read next are `:h neotest`, which the README points at for design and programmatic use, and the adapter's own README, which carries the setup instructions that matter for your runner.

## FAQ

### What is neotest?

It is an extensible framework for interacting with tests within Neovim, written in Lua and released under the MIT licence. Neotest finds test positions, runs them and reports status, but it does not know any particular test framework. Support for pytest, jest, go, rspec, cargo and the rest comes from separate adapter plugins you install alongside it.

### What does neotest need before it can run my tests?

Three things. An adapter plugin for your runner, nvim-nio as a required dependency, and in most cases a treesitter parser for the language your tests are written in. FixCursorHold.nvim is also recommended so that status signs refresh often without lowering updatetime, which otherwise causes excessive writes to disk.

### Why does neotest depend on the CursorHold event?

Status signs are attached to lines as you move the cursor, and CursorHold is the event that makes that happen without an explicit command. The catch is that CursorHold fires on the updatetime timer, so a responsive sign display and a quiet editor pull in opposite directions. FixCursorHold.nvim decouples the two by generating the event independently of updatetime.

### Does neotest support my language, and what if there are two adapters?

The README carries a table of roughly forty runners and their adapters, and some languages list more than one because the adapters differ in how they locate tests. Go has neotest-go and neotest-golang, .NET has a treesitter adapter and a vstest one, and Rust points at both neotest-rust and rustaceanvim. Read the adapter READMEs to pick, since the core table does not compare them.

### Is neotest safe to depend on in a stable config?

The README calls it early stage software, so treat the API as something that can move. The maintenance signals are healthy: the project is not archived, the last push was on 2026-08-16, and releases in July 2026 removed a tree-sitter dependency and tuned parse options. Pinning to a release rather than tracking master keeps you insulated from the kind of change the warning is about.

## Sources

- [Issues](https://github.com/nvim-neotest/neotest/issues)
- [License: MIT](https://github.com/nvim-neotest/neotest/blob/master/LICENSE)
- [nvim-neotest/neotest on GitHub](https://github.com/nvim-neotest/neotest)
- [README](https://github.com/nvim-neotest/neotest/blob/master/README.md)
- [Releases](https://github.com/nvim-neotest/neotest/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nvim-neotest-neotest
