Skip to content

Recipes

Debugging

To debug tests, make sure you depend on mfussenegger/nvim-dap and rcarriga/nvim-dap-ui.

Then you have two options:

  • DAP configuration provided by leoluz/nvim-dap-go (recommended)
  • Use your own custom DAP configuration (no additional dependency needed)
Adapter-provided (recommended)
Diff
return {
+  {
+    "rcarriga/nvim-dap-ui",
+    dependencies = {
+      "nvim-neotest/nvim-nio",
+      "mfussenegger/nvim-dap",
+    },
+  },
+
  {
    "nvim-neotest/neotest",
    dependencies = {
      "nvim-neotest/nvim-nio",
      "nvim-lua/plenary.nvim",
      "antoinemadec/FixCursorHold.nvim",
      "nvim-treesitter/nvim-treesitter",
-      "fredrikaverpil/neotest-golang", -- Installation
+      {
+        "fredrikaverpil/neotest-golang", -- Installation
+        dependencies = {
+          "leoluz/nvim-dap-go",
+        },
+      },
    },
    config = function()
      require("neotest").setup({
        adapters = {
          require("neotest-golang"), -- Registration
        },
      })
    end,
  },
}
Use your own custom DAP configuration
Diff
return {
+  {
+    "rcarriga/nvim-dap-ui",
+    dependencies = {
+      "nvim-neotest/nvim-nio",
+      "mfussenegger/nvim-dap",
+    },
+  },
+
  {
    "nvim-neotest/neotest",
    dependencies = {
      "nvim-neotest/nvim-nio",
      "nvim-lua/plenary.nvim",
      "antoinemadec/FixCursorHold.nvim",
      "nvim-treesitter/nvim-treesitter",
      "fredrikaverpil/neotest-golang", -- Installation
    },
    config = function()
+      local options = {
+        dap_mode = "manual",
+        dap_manual_config = {
+          name = "Debug go tests",
+          type = "go", -- Preconfigured DAP adapter name
+          request = "launch",
+          mode = "test",
+        },
+      }
      require("neotest").setup({
        adapters = {
+         require("neotest-golang")(options) -- Registration
        },
      })
    end,
  },
}

Finally, set keymaps to run Neotest commands.

Keymap for debugging nearest test

Lua
return {
  {
    "nvim-neotest/neotest",
    -- ...
    keys = {
      {
        "<leader>td",
        function()
          require("neotest").run.run({ suite = false, strategy = "dap" })
        end,
        desc = "Debug nearest test",
      },
    },
  },
}

Coverage

You can use andythigpen/nvim-coverage to show coverage in Neovim.

Coverage

Lua
return {
  {
    "nvim-neotest/neotest",
    dependencies = {
      "nvim-neotest/nvim-nio",
      "nvim-lua/plenary.nvim",
      "antoinemadec/FixCursorHold.nvim",
      "nvim-treesitter/nvim-treesitter",
      {
        "fredrikaverpil/neotest-golang",
        version = "*",
        dependencies = {
          "andythigpen/nvim-coverage", -- Added dependency
        },
      },
    },
    config = function()
      local neotest_golang_opts = {  -- Specify configuration
        runner = "go",
        go_test_args = {
          "-v",
          "-race",
          "-count=1",
          "-coverprofile=" .. vim.fn.getcwd() .. "/coverage.out",
        },
      }
      require("neotest").setup({
        adapters = {
          require("neotest-golang")(neotest_golang_opts), -- Registration
        },
      })
    end,
  },
}

Custom test arguments

You can pass custom arguments, such as build tags, into the adapter either by supplying a configuration or as keymap/command.

Using configuration

If you need to set build tags (like e.g. -tags debug or -tags "tag1 tag2"), you need to provide these arguments both in the go_test_args and go_list_args adapter options. If you want to be able to debug, you also need to set dap_go_opts. Full example:

Build tags

Lua
return {
  {
    "nvim-neotest/neotest",
    config = function()
      require("neotest").setup({
        adapters = {
          require("neotest-golang")({
            go_test_args = { "-count=1", "-tags=integration" },
            go_list_args = { "-tags=integration" },
            dap_go_opts = {
              delve = {
                build_flags = { "-tags=integration" },
              },
            },
          }),
        },
      })
    end,
  },
}

Using keymap/command

One can override go_test_args by passing in the extra_args.go_test_args table. Using this approach, you can set up multiple keymaps or run tests with different arguments without restarting Neovim.

For instance, if you want to run go test with the -v -race -count=1 -p=1 -parallel=10 -tags=integration flags, you could call:

Extra args

Lua
require('neotest').run.run(
  {
    vim.fn.expand('%'),
    extra_args = {
      go_test_args = {
        "-v",
        "-race",
        "-count=1",
        "-p=1",
        "-parallel=10",
        "-tags=integration",
      },
    },
  },
)

Limited support

Currently, overriding go list or DAP arguments via extra_args is not currently supported but could easily be implemented in a similar way, if needed.

Custom environment variables

You can also pass in custom environment variables to the adapter, which will be set when running tests. This can be useful for setting up test-specific environment variables, such as database connection strings or API keys.

Using configuration

You can pass in environment variables by providing a table of key-value pairs to the env option in the adapter configuration.

Custom env variables

Lua
return {
  {
    "nvim-neotest/neotest",
    config = function()
      require("neotest").setup({
        adapters = {
          require("neotest-golang")({
            env = {
              TEST_VAR1 = "test1",
              TEST_VAR2 = "test2",
            },
          }),
        },
      })
    end,
  },
}

Using keymap/command

You can also pass in environment variables via the extra_args.env table when running tests. This allows you to set environment variables dynamically at runtime, without needing to restart Neovim.

Custom env variables via extra_args

Lua
require('neotest').run.run(
  {
    vim.fn.expand('%'),
    extra_args = {
      env = {
        TEST_VAR1 = "test1",
        TEST_VAR2 = "test2",
      },
    },
  },
)

Pass arguments as function instead of table

Some use cases may require you to pass in dynamically generated arguments during runtime. To cater for this, you can provide arguments as a function.

Args passed as functions

Lua
return {
  {
    "nvim-neotest/neotest",
    config = function()
      require("neotest").setup({
        adapters = {
          require("neotest-golang")({
            go_test_args = function()
              -- provide custom logic here..
              return { "-count=1", "-tags=integration" }
            end,
            go_list_args = function()
              -- provide custom logic here..
              return { "-tags=integration" }
            end,
            dap_go_opts = function()
              -- provide custom logic here..
              return {
                delve = {
                  build_flags = { "-tags=integration" },
                },
              }
            end,
            },
          }),
        },
      })
    end,
  },
}

Per-project configuration

Depending on how you have Neovim setup, you can define the neotest-golang config on a per-project basis by placing a .lazy.lua with overrides in the project. This requires the lazy.nvim plugin manager.

Example configuration: extra everything

In the below code block, I've provided a pretty hefty configuration example, which includes the required setup for testing and debugging along with all the keymaps. This is a merged snapshot of my own config, which I hope you can draw inspiration from. To view my current config, which is divided up into several files, see:

Extra everything

Lua
return {

  -- Neotest setup
  {
    "nvim-neotest/neotest",
    event = "VeryLazy",
    dependencies = {
      "nvim-neotest/nvim-nio",
      "nvim-lua/plenary.nvim",
      "antoinemadec/FixCursorHold.nvim",
      "nvim-treesitter/nvim-treesitter",

      "nvim-neotest/neotest-plenary",
      "nvim-neotest/neotest-vim-test",

      {
        "fredrikaverpil/neotest-golang",
        dependencies = {
          {
            "leoluz/nvim-dap-go",
            opts = {},
          },
        },
        branch = "main",
      },
    },
    opts = function(_, opts)
      opts.adapters = opts.adapters or {}
      opts.adapters["neotest-golang"] = {
        go_test_args = {
          "-v",
          "-race",
          "-coverprofile=" .. vim.fn.getcwd() .. "/coverage.out",
        },
      }
    end,
    config = function(_, opts)
      if opts.adapters then
        local adapters = {}
        for name, config in pairs(opts.adapters or {}) do
          if type(name) == "number" then
            if type(config) == "string" then
              config = require(config)
            end
            adapters[#adapters + 1] = config
          elseif config ~= false then
            local adapter = require(name)
            if type(config) == "table" and not vim.tbl_isempty(config) then
              local meta = getmetatable(adapter)
              if adapter.setup then
                adapter.setup(config)
              elseif adapter.adapter then
                adapter.adapter(config)
                adapter = adapter.adapter
              elseif meta and meta.__call then
                adapter(config)
              else
                error("Adapter " .. name .. " does not support setup")
              end
            end
            adapters[#adapters + 1] = adapter
          end
        end
        opts.adapters = adapters
      end

      require("neotest").setup(opts)
    end,
    keys = {
      { "<leader>ta", function() require("neotest").run.attach() end, desc = "[t]est [a]ttach" },
      { "<leader>tf", function() require("neotest").run.run(vim.fn.expand("%")) end, desc = "[t]est run [f]ile" },
      { "<leader>tA", function() require("neotest").run.run(vim.uv.cwd()) end, desc = "[t]est [A]ll files" },
      { "<leader>tS", function() require("neotest").run.run({ suite = true }) end, desc = "[t]est [S]uite" },
      { "<leader>tn", function() require("neotest").run.run() end, desc = "[t]est [n]earest" },
      { "<leader>tl", function() require("neotest").run.run_last() end, desc = "[t]est [l]ast" },
      { "<leader>ts", function() require("neotest").summary.toggle() end, desc = "[t]est [s]ummary" },
      { "<leader>to", function() require("neotest").output.open({ enter = true, auto_close = true }) end, desc = "[t]est [o]utput" },
      { "<leader>tO", function() require("neotest").output_panel.toggle() end, desc = "[t]est [O]utput panel" },
      { "<leader>tt", function() require("neotest").run.stop() end, desc = "[t]est [t]erminate" },
      { "<leader>td", function() require("neotest").run.run({ suite = false, strategy = "dap" }) end, desc = "Debug nearest test" },
      { "<leader>tD", function() require("neotest").run.run({ vim.fn.expand("%"), strategy = "dap" }) end, desc = "Debug current file" },
    },
  },

  -- DAP setup
  {
    "mfussenegger/nvim-dap",
    event = "VeryLazy",
    keys = {
      {"<leader>db", function() require("dap").toggle_breakpoint() end, desc = "toggle [d]ebug [b]reakpoint" },
      {"<leader>dB", function() require("dap").set_breakpoint(vim.fn.input("Breakpoint condition: ")) end, desc = "[d]ebug [B]reakpoint"},
      {"<leader>dc", function() require("dap").continue() end, desc = "[d]ebug [c]ontinue (start here)" },
      {"<leader>dC", function() require("dap").run_to_cursor() end, desc = "[d]ebug [C]ursor" },
      {"<leader>dg", function() require("dap").goto_() end, desc = "[d]ebug [g]o to line" },
      {"<leader>do", function() require("dap").step_over() end, desc = "[d]ebug step [o]ver" },
      {"<leader>dO", function() require("dap").step_out() end, desc = "[d]ebug step [O]ut" },
      {"<leader>di", function() require("dap").step_into() end, desc = "[d]ebug [i]nto" },
      {"<leader>dj", function() require("dap").down() end, desc = "[d]ebug [j]ump down" },
      {"<leader>dk", function() require("dap").up() end, desc = "[d]ebug [k]ump up" },
      {"<leader>dl", function() require("dap").run_last() end, desc = "[d]ebug [l]ast" },
      {"<leader>dp", function() require("dap").pause() end, desc = "[d]ebug [p]ause" },
      {"<leader>dr", function() require("dap").repl.toggle() end, desc = "[d]ebug [r]epl" },
      {"<leader>dR", function() require("dap").clear_breakpoints() end, desc = "[d]ebug [R]emove breakpoints" },
      {"<leader>ds", function() require("dap").session() end, desc ="[d]ebug [s]ession" },
      {"<leader>dt", function() require("dap").terminate() end, desc = "[d]ebug [t]erminate" },
      {"<leader>dw", function() require("dap.ui.widgets").hover() end, desc = "[d]ebug [w]idgets" },
    },
  },

  -- DAP UI setup
  {
    "rcarriga/nvim-dap-ui",
    event = "VeryLazy",
    dependencies = {
      "nvim-neotest/nvim-nio",
      "mfussenegger/nvim-dap",
    },
    opts = {},
    config = function(_, opts)
      -- setup dap config by VsCode launch.json file
      -- require("dap.ext.vscode").load_launchjs()
      local dap = require("dap")
      local dapui = require("dapui")
      dapui.setup(opts)
      dap.listeners.after.event_initialized["dapui_config"] = function()
        dapui.open({})
      end
      dap.listeners.before.event_terminated["dapui_config"] = function()
        dapui.close({})
      end
      dap.listeners.before.event_exited["dapui_config"] = function()
        dapui.close({})
      end
    end,
    keys = {
      { "<leader>du", function() require("dapui").toggle({}) end, desc = "[d]ap [u]i" },
      { "<leader>de", function() require("dapui").eval() end, desc = "[d]ap [e]val" },
    },
  },
  {
    "theHamsta/nvim-dap-virtual-text",
    opts = {},
  },
}