Being able to run your unit tests quickly from Vim is vital when doing test-driven development. I've often seen developers using Vim to write code, and switching to a different terminal to run tests. This typically involves typing out the path to the test file that we want to run, which becomes unwieldy.
Most programming languages have a plugin that allows you to run unit tests from within Vim. For example, with Ruby there is the vim-rspec. Although these plugins are very helpful and do a good job, this dependency on a third-party plugin can become problematic when pairing on someone else's computer.
In this post, we'll describe a few ways to run tests without language-specific plugins. We'll also describe, with the help of a few general purpose plugins, how to run tests in an optimal way.
Running tests using the shell from Vim
In Vim, we can interact with the shell from Command Line Mode by typing
:! followed by some shell command. So for example
:!pwd will temporarily exit Vim, print the working directory, before allowing you to jump back into Vim.
When using Ruby and RSpec, the command to run a test is usually something like
rspec spec/some_test.rb. The filename for the current file that you're editing is mapped to
%, so we can echo the current file using
:!echo % or get the line count with
:!wc -l %. If we are on the test file that we want to run, we can execute the tests with RSpec as follows:
The thing that I like about this approach is that it works with vanilla Vim that has zero configuration. You can run Vim with no configuration by starting
vim -u NONE to see for yourself.
We can also map this to a shortcut to make it faster to run. I'll be mapping it to
,rt as comma is my leader, and
rt as a mnemonic to "run tests". We can do that as follows:
:nnoremap <leader>rt :!rspec %<cr>
,rt now runs the unit tests for the current spec file that I'm working on.
And we can also run all tests with rake if your project is using that with the following (I'm using
,ra here to "run all");
nnoremap <leader>ra :!rake spec<cr>
Running an individual test
RSpec allows us to run a single test by specifying the line number in the command. So
rspec some_test.rb:10 will run whatever test is at line 10.
We can concatenate the current line number with the filename using a
.. So doing
echo "!rspec %:" . line(".") will return "!rspec %:1" if we are on the first line. We'd like to execute this string. The
execute command takes in a string as input, and executes the contents of it. We can add a shortcut for running a specific test as follows;
nnoremap <leader>rs :execute "!rspec %:" . line(".")<cr>
What we've seen so far is a basic way of running tests without the help of any plugins, which is great for when you're pairing with someone.
This approach has several limitations. We lose the Vim window whilst the tests are running, which means we keep briefly losing context. Also, we have to wait for tests to be run to completion before doing anything else.
Running tests asynchronously with Vimux (requires tmux to be used)
With Vimux (and tmux), we can run commands inside a separate tmux pane in a split screen format. This is useful when running tests, as the output of the tests is usually displayed at the bottom of the screen. We can run tests with the following commands;
VimuxRunCommand("clear; bundle exec rspec " . expand("%"))
VimuxRunCommand("clear; bundle exec rspec " . expand("%") . ":" . line("."))
These can again be mapped to a shortcut of your choosing.
The issues I have experienced with Vimux is that it requires some familiarity of tmux, and I've found that makes pairing sometimes quite challenging. I usually have to give a guided tour of tmux and my configuration to allow my pair to do basic things like navigating to a window. I'm also unable to interact with the error message. When something does fail, it'll be nice to be able to navigate back to Vim by clicking on a line number in the error message. An IDE typically gives you a stacktrace that you can click on to navigate the codebase. I'm not aware of how to do this with Vimux/tmux.
Running tests with
:make with the quickfix list
We can instead use the Vim command
:make, which will run our unit tests and parse any errors/test failures that occur into Vim's quickfix list. The quickfix list can then be used to navigate to various places where the tests are failing.
As we're trying to run an RSpec test, we first must set the compiler to be
rspec. This states what command will be run when we execute
make, and it also defines the error format so any test failures can be parsed into the quickfix list.
Set the compiler to RSpec:
And then run the current file with
make, which can be done as follows:
If a test fails, you can open up the quickfix list by using
:copen. You can then navigate to where the tests failed from the quickfix list.
Or, we could instead use something like rake.
To see all compilers you have available, run
:compiler. You can download more compilers by installing something like vim-polyglot.
However, we still have the problem of losing the Vim window whilst tests are being run.
Best of both worlds. Asynchronous testing using Vim Dispatch
I find Vim Dispatch to be the best option for running tests in Vim. When using tmux, it allows me to run tests asynchronously whilst seeing the current test output in a split screen—I can see how many tests are failing, for example. But how it differs to something like Vimux is that as the tests have finished running, the tmux pane is exited, and the quickfix list is subsequently populated with any test failures. If no tests fail, then the window disappears.
:Dispatch rspec %
:execute "Dispatch rspec %:" . line(".")
I prefer using Vim Dispatch instead of language-specific plugins because I feel more in control of what exactly is being run. I can move to a different language and just change what test command gets run.
Setting language-specific configuration
We could put these keymappings in our .vimrc file, but this will only work for Ruby projects. Vim provides us with the ability to set language-specific configurations.
Instead of putting this in the
~/.vimrc file, we can add this keymap to
~/.vim/ftplugin/ruby.vim, which will make this shortcut only valid for Ruby files. That way, if you want to run tests using Clojure or Elixir, you can put these keymappings in
~/.vim/ftplugin/elixir.vim instead, and just change the test command that run. More information on ftplugin can be found here.