Skip to content

julienvincent/clojure-test.nvim

Repository files navigation

clojure-test.nvim

Run clojure tests interactively in your repl directly from neovim.


clojure-test-example

This is a plugin to make working with Clojure tests within Neovim a lot more user-friendly. Here are some of the features:

  • Interactively run tests from anywhere in the project
    • Provides selection UI's for picking namespaces or individual tests
  • Easily re-run previous or failing tests without having to navigate back to test namespaces
  • Provide a more human-readable interface
  • Render exceptions in a more human friendly manner
  • Allow go-to-definition on test reports
    • Go to where an exception was thrown
    • Go to failing tests
  • Allow running hooks before executing tests
    • Save files and or reload changed namespaces

See the feature demo for a quick overview.

Warning

This is very alpha software. It's built for my personal use, and I am developing it out as I go. Use at your own risk.

Installation

{
  "julienvincent/clojure-test.nvim",
  config = function()
    require("clojure-test").setup()
  end
}

Neovim Dependencies

Clojure Dependencies

Clojars Project

Note

This plugin currently requires the io.julienvincent/clojure-test companion clojure dependency to be available on your classpath.

Configure an alias in the project or user level deps.edn file to include the appropriate dependencies. For example you could modify your $XDG_CONFIG_HOME/clojure/deps.edn or $HOME/.clojure/deps.edn file as follows:

{:aliases
 {:nrepl {:extra-deps {;; Assuming you already have something like this
                       nrepl/nrepl {:mvn/version "1.0.0"}
                       cider/cider-nrepl {:mvn/version "0.42.1"}

                       ;; Add the companion dependency
                       io.julienvincent/clojure-test {:mvn/version "RELEASE"}}
          :main-opts  ["--main" "nrepl.cmdline"
                       "--middleware" "[cider.nrepl/cider-middleware]"
                       "--interactive"]}}}

Or alternatively you can include them inline with the following clojure command

clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version "1.0.0"}
                        cider/cider-nrepl {:mvn/version "0.28.5"}
                        io.julienvincent/clojure-test {:mvn/version "RELEASE"}}}' \
        -M -m nrepl.cmdline \
        --middleware '[cider.nrepl/cider-middleware]'

Why the Clojure dependency?

This plugin currently requires executing a lot of logic on the Clojure side, and it needs to format/parse input and output in a way that is easily compatible with the Lua plugin - which requires other transient dependencies.

I would like for a future version of this plugin to not require this companion dependency but for the moment it is the simplest approach.

Configuration

local clojure_test = require("clojure-test")
clojure_test.setup({
  -- list of default keybindings
  keys = {
    ui = {
      expand_node = { "l", "<Right>" },
      collapse_node = { "h", "<Left>" },
      go_to = { "<Cr>", "gd" },

      cycle_focus_forwards = "<Tab>",
      cycle_focus_backwards = "<S-Tab>",

      quit = { "q", "<Esc>" },
    },
  },

  hooks = {
    -- This is a hook that will be called with a table of tests that are about to be run. This
    -- can be used as an opportunity to save files and/or reload clojure namespaces.
    --
    -- This combines really well with https://github.com/tonsky/clj-reload
    before_run = function(tests)
    end
  }
})

Usage

Once installed you can call the various API methods to run and load tests or setup key bindings to do this for you.

local api = require("clojure-test.api")

vim.keymap.set("n", "<localleader>ta", api.run_all_tests, { desc = "Run all tests" })
vim.keymap.set("n", "<localleader>tr", api.run_tests, { desc = "Run tests" })
vim.keymap.set("n", "<localleader>tn", api.run_tests_in_ns, { desc = "Run tests in a namespace" })
vim.keymap.set("n", "<localleader>tp", api.rerun_previous, { desc = "Rerun the most recently run tests" })
vim.keymap.set("n", "<localleader>tl", api.load_tests, { desc = "Find and load test namespaces in classpath" })
vim.keymap.set("n", "<localleader>!", function() api.analyze_exception("*e") end, { desc = "Inspect the most recent exception" })

Feature Demo

clojure-test-demo

Reload namespaces before run

A very useful configuration snippet is to set up a hook to save modified buffers and reload namespaces before running tests. This means that while you are working on changes you can just run the tests that cover your changes without having to go back and re-eval everything.

This example makes use of tonsky/clj-reload which ensures that namespaces are reloaded in the order they depend on each other.

require("clojure-test").setup({
  hooks = {
    before_run = function(_)
      -- write all modified buffers
      vim.api.nvim_command("wa")

      local client = require("conjure.client")
      local fn = require("conjure.eval")["eval-str"]
      client["with-filetype"]("clojure", fn, {
        origin = "clojure_test.hooks.before_run",
        context = "user",
        code = [[ ((requiring-resolve 'clj-reload.core/reload)) ]],
      })
    end
  }
})

Architecture

This is still TBD

Custom Backends

This plugin uses a configurable backend protocol to communicate with the Clojure nREPL server. This backend is responsible for performing discovery of test namespaces and tests, as well as executing these tests and providing back the test reports.

By default, clojure-test is configured to use a built-in Conjure REPL backend. You can find the relevant implementations here and here.

As an example, here is how the default backend is configured:

local backends = require("clojure-test.backends")
local clients = require("clojure-test.clients")

require("clojure-test").setup({
  backend = backends.repl.create(clients.conjure),
})

Or you can implement your own backend.