- Supported Test Frameworks
- GoogleTest (v1.11.0+): Supports macros
TEST
,TEST_F
andTEST_P
- For
TEST_P
, onlyRange
,Values
andBool
parameter generators are supported. See INSTANTIATE_TEST_SUITE_P for more.
- For
- Catch2 (v3.3.0+): Supports macros
TEST_CASE
,TEST_CASE_METHOD
,SCENARIO
- doctest (v2.4.8+): Supports macros
TEST_CASE
,TEST_CASE_FIXTURE
,SCENARIO
- Decorators not supported yet
- CppUTest (from commit
f2016778dbf385b99d676f3f46e1153427112be1
and onwards): Supports macrosTEST
- Configure the CMake build with
set(CPPUTEST_TESTS_DETAILED ON)
to properly enumerate the tests.
- Configure the CMake build with
- GoogleTest (v1.11.0+): Supports macros
- Automatically detects test framework used in a test file (see limitations)
- Automatically detects CTest test directory (see limitations)
- Parses test results and displays errors as diagnostics (if you have enabled neotest's diagnostic option)
The framework versions listed above are the ones that have been tested, but older versions may work as well.
- Does not compile any source or tests (cmake-tools is highly recommended as a companion plugin).
- While CTest does not directly depend on the usage of CMake, this plugin assumes you have enumerated your tests with
CMake integrations such as
gtest_discover_tests()
,catch_discover_tests()
, etc. CTestTestfile.cmake
is expected to be on path from project root (max two levels deep)- For instance
<dir>/CTestTestfile.cmake
or<dir>/<config>/CTestTestfile.cmake
. - For multi-config projects, the first CTest enabled configuration found will be selected.
- For instance
- Some of the frameworks, such as
catch2
anddoctest
, enumerates test case names interchangeably. This makes it impossible forneotest-ctest
to reliably map them to neotest positions. Please ensure that test case names are uniquely defined if you use multiple frameworks together! - Does not support neotest's
dap
strategy for debugging tests (yet)
- Neovim v0.9.1+, v0.10.x, or nightly.
- nvim-neotest v5.0.0+
- Tree-sitter parser for C++ to be installed (preferably latest).
- CMake v3.21 or higher (CTest is bundled with CMake)
See Neotest Installation Instructions.
The following example is based on lazy.nvim
:
{
"nvim-neotest/neotest",
dependencies = {
"nvim-lua/plenary.nvim",
-- Other neotest dependencies here
"orjangj/neotest-ctest",
},
config = function()
-- Optional, but recommended, if you have enabled neotest's diagnostic option
local neotest_ns = vim.api.nvim_create_namespace("neotest")
vim.diagnostic.config({
virtual_text = {
format = function(diagnostic)
-- Convert newlines, tabs and whitespaces into a single whitespace
-- for improved virtual text readability
local message = diagnostic.message:gsub("[\r\n\t%s]+", " ")
return message
end,
},
}, neotest_ns)
require("neotest").setup({
adapters = {
-- Load with default config
require("neotest-ctest").setup({})
}
})
end
}
require("neotest-ctest").setup({
-- fun(string) -> string: Find the project root directory given a current directory
-- to work from.
root = function(dir)
-- by default, it will use neotest.lib.files.match_root_pattern with the following entries
return require("neotest.lib").files.match_root_pattern(
-- NOTE: CMakeLists.txt is not a good candidate as it can be found in
-- more than one directory
"CMakePresets.json",
"compile_commands.json",
".clangd",
".clang-format",
".clang-tidy",
"build",
"out",
".git"
)(dir)
end
),
-- fun(string) -> bool: Takes a file path as string and returns true if it contains tests.
-- This function is called often by neotest, so make sure you don't do any heavy duty work.
is_test_file = function(file)
-- by default, returns true if the file stem ends with _test and the file extension is
-- one of cpp/cc/cxx.
end,
-- fun(string, string, string) -> bool: Filter directories when searching for test files.
-- Best to keep this as-is and set per-project settings in neotest instead.
-- See :h neotest.Config.discovery.
filter_dir = function(name, rel_path, root)
-- If you don't configure filter_dir through neotest, and you leave it as-is,
-- it will filter the following directories by default: build, cmake, doc,
-- docs, examples, out, scripts, tools, venv.
end,
-- What frameworks to consider when performing auto-detection of test files.
-- Priority can be configured by ordering/removing list items to your needs.
-- By default, each test file will be queried with the given frameworks in the
-- following order.
frameworks = { "gtest", "catch2", "doctest", "cpputest"},
-- What extra args should ALWAYS be sent to CTest? Note that most of CTest arguments
-- are not expected to be used (or work) with this plugin, but some might be useful
-- depending on your needs. For instance:
-- extra_args = {
-- "--stop-on-failure",
-- "--schedule-random",
-- "--timeout",
-- "<seconds>",
-- }
-- If you want to send extra_args for one given invocation only, send them to
-- `neotest.run.run({extra_args = ...})` instead. see :h neotest.RunArgs for details.
extra_args = {},
})
It's possible to configure the adapter per project using Neotest's projects
option if you need more fine-grained
control:
require("neotest").setup({
-- other options
projects = {
["~/path/to/some/project"] = {
discovery = {
filter_dir = function(name, rel_path, root)
-- Do not look for tests in `build` folder for this specific project
return name ~= "build"
end,
},
adapters = {
require("neotest-ctest").setup({
is_test_file = function(file_path)
-- your implementation
end,
frameworks = { "catch2" },
}),
},
},
},
})
See Neotest Usage. The following example of keybindings can be used as a starting point:
{
"nvim-neotest/neotest",
dependencies = {
"nvim-lua/plenary.nvim",
-- Other neotest dependencies here
"orjangj/neotest-ctest",
},
keys = function()
local neotest = require("neotest")
return {
{ "<leader>tf", function() neotest.run.run(vim.fn.expand("%")) end, desc = "Run File" },
{ "<leader>tt", function() neotest.run.run() end, desc = "Run Nearest" },
{ "<leader>tw", function() neotest.run.run(vim.loop.cwd()) end, desc = "Run Workspace" },
{
"<leader>tr",
function()
-- This will only show the output from the test framework
neotest.output.open({ short = true, auto_close = true })
end,
desc = "Results (short)",
},
{
"<leader>tR",
function()
-- This will show the classic CTest log output.
-- The output usually spans more than can fit the neotest floating window,
-- so using 'enter = true' to enable normal navigation within the window
-- is recommended.
neotest.output.open({ enter = true })
end,
desc = "Results (full)",
},
-- Other keybindings
}
end,
config = function()
require("neotest").setup({
adapters = {
-- Load with default config
require("neotest-ctest").setup({})
}
})
end
}