
Quickstart: Angular 19 Testing with Jest and Neovim
Looking to simplify testing in your Angular 19 monorepo and integrate it with Neovim? This guide will show you how to set up Jest, configure Angular for Jest, and use Neotest with the neotest-jest adapter to run tests directly in your editor.
Demo & Source Code
Here is the source code for the sample app used in this guide: GitHub Repo.
Introduction
- Goal: Migrate from Karma/Jasmine to Jest in an Angular 19 Monorepo and configure Neovim to leverage Neotest and the neotest-jest adapter.
- Why Jest?
- Simpler configuration
- Faster test runs
- Large and active community support
- Why Neotest?
- Inline test results
- Easy test discovery and management within Neovim
With this approach, you’ll gain a modern testing setup and an efficient, editor-integrated workflow.
Prerequisites
- Angular 19 Monorepo: Ensure your Angular application and libraries are organized in a monorepo. If not, use
npm init @angular -- --no-create-application
to set one up. More parameters can be found on the Angular CLI documentation page. - Node.js & npm (or yarn/pnpm): Ensure you have a recent version installed.
- Neovim: Ensure you’re using a version that supports Lua-based configurations (≥ 0.8 recommended). If you’re using LazyVim, it provides an excellent starting point for modern Neovim setups.
Removing Karma and Jasmine
Uninstall
First, remove all Karma- or Jasmine-related packages from your monorepo:
npm uninstall @types/jasmine jasmine-core karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter
Update angular.json
Open your angular.json
file and locate the test
section under the architect
block that references Karma or Jasmine. Remove or comment out these entries, as they are no longer needed. Here’s an example:
{
// ...
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
// ...
}
}
}
Clean Up Test Scripts
In your package.json
, look for "scripts"
entries that invoke Karma (e.g., "ng test"
or anything referencing "karma.conf.js"
). Remove them or replace them with Jest commands, which we’ll set up next.
{
"scripts": {
"test": "jest"
}
}
Jest & Angular
Install
Set up Jest for Angular 19 by installing the following packages:
npm install --save-dev jest @types/jest jest-preset-angular ts-node
Project Configuration
In your monorepo, each Angular project (app or library) may have unique testing requirements. Instead of using a single top-level Jest configuration, it’s recommended to have a dedicated jest.config.ts file for each project. This approach allows you to tailor the testing setup to the specific needs of each package while maintaining better modularity and flexibility.
Config
Create a jest.config.ts
file in each project folder, such as /projects/app
, within your monorepo to enable project-specific configurations:
import type { Config } from "jest";
const config: Config = {
preset: "jest-preset-angular",
setupFilesAfterEnv: ["<rootDir>/setup-jest.ts"],
};
export default config;
Setup
The setup-jest.ts
file, like the jest.config.ts
file, should be placed in each project folder within your monorepo to ensure project-specific test environment configurations.
Include the following code in the setup-jest.ts file as it is required:
import { setupZoneTestEnv } from "jest-preset-angular/setup-env/zone";
setupZoneTestEnv();
Types
Replace "types": ["jasmine"]
with "types": ["jest"]
in your tsconfig.spec.json
to switch from Jasmine to Jest type definitions:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": ["jest"]
},
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
}
Verifying the Jest Setup
Before diving into Neovim configuration, make sure Jest works:
1. Sample Test
Check if a test file exists (e.g., app.component.spec.ts
), and create one if it doesn’t.
2. Run Tests
cd projects/demo
npx jest
3. Validation
Verify that Jest executes successfully and your tests pass (or fail, if intended). It should look something like this:
PASS src/app/app.component.spec.ts
AppComponent
✓ should create the app (112 ms)
✓ should have the demo title (12 ms)
✓ should render title (19 ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.477 s, estimated 2 s
Setting Up Neovim for Neotest
Enable Neotest (LazyVim Extras)
Neotest is a Neovim plugin that streamlines test discovery, execution, and result visualization - all from within your editor. To enable the Neotest framework in LazyVim, add the test.core`` extras to your LazyVim configuration. In many setups, this is done by editing
~/.config/nvim/lua/config/lazy.lua` (or whichever Lua file you’ve designated for LazyVim plugin declarations):
return {
-- This line imports LazyVim’s built-in testing extras, which includes nvim-neotest/neotest:
{ import = "lazyvim.plugins.extras.test.core" },
-- Other LazyVim or custom plugins go here
}
When Neovim loads, LazyVim will automatically pull in nvim-neotest/neotest
and any related tooling, such as the default test adapters. You can then configure additional details—like the specific neotest-jest
adapter—in separate plugin configuration files.
Validation
Press <leader>t
in Neovim to confirm Neotest is enabled; a test command overlay should appear:
Configure Neotest-Jest
To connect your Jest-based tests with Neotest, you need to create or edit the plugin configuration for the neotest-jest
adapter. For example, in ~/.config/nvim/lua/plugins/test.lua
:
return {
"nvim-neotest/neotest",
dependencies = {
"nvim-neotest/neotest-jest",
},
opts = {
adapters = {
["neotest-jest"] = {
jestConfigFile = function(path)
return require("utils.path").get_project_root(path) .. "jest.config.ts"
end,
env = { CI = true },
cwd = function(path)
return require("utils.path").get_project_root(path)
end,
},
},
},
-- stylua: ignore
keys = {
{ "<leader>t", "", desc = "+test" },
{ "<leader>tt", function() require("neotest").run.run(vim.fn.expand("%")) end, desc = "Run File" },
{ "<leader>tT", function() require("neotest").run.run(require("utils.path").current_project_root()) end, desc = "Run Project" },
{ "<leader>tA", function() require("neotest").run.run(vim.uv.cwd()) end, desc = "Run All" },
{ "<leader>tr", function() require("neotest").run.run() end, desc = "Run Nearest" },
{ "<leader>tl", function() require("neotest").run.run_last() end, desc = "Run Last" },
{ "<leader>ts", function() require("neotest").summary.toggle() end, desc = "Toggle Summary" },
{ "<leader>to", function() require("neotest").output.open({ enter = true, auto_close = true }) end, desc = "Show Output" },
{ "<leader>tO", function() require("neotest").output_panel.toggle() end, desc = "Toggle Output Panel" },
{ "<leader>tS", function() require("neotest").run.stop() end, desc = "Stop" },
{ "<leader>tw", function() require("neotest").watch.toggle(vim.fn.expand("%")) end, desc = "Toggle Watch" },
},
}
This setup includes:
- Run Project (
<leader>tT
): Runs all tests in the current project. - Run All (
<leader>tA
): Executes all tests across the workspace. - Dynamic resolution of the Jest configuration file and working directory based on the file path.
utils.path
The utils.path
module is a custom Lua utility that dynamically determines the project root and ensures correct paths for monorepo structures. Save it in ~/.config/nvim/lua/utils/path.lua
:
local M = {}
function M.find_root(path)
local root = path:match("(.-/[^/]+/)src")
if root then
return root
end
root = path:match("(.-/(projects)/[^/]+)") or path:match("(.-/(apps)/[^/]+)") or path:match("(.-/(libs)/[^/]+)")
if root then
return root
end
return vim.fn.getcwd()
end
function M.get_absolute_path(path)
return vim.fn.fnamemodify(path, ":p")
end
function M.get_project_root(file_path)
return M.get_absolute_path(M.find_root(file_path))
end
function M.current_project_root()
return M.get_project_root(vim.fn.expand("%"))
end
return M
By leveraging utils.path
, this setup ensures accurate handling of jest.config.ts
and the working directory in a monorepo.
Run All Tests
If you want to run tests across all files in the entire monorepo, you can set up shared configurations at the root level. This is optional but useful for running all tests with a single command. Here’s an example:
import type { Config } from "jest";
const config: Config = {
preset: "jest-preset-angular",
roots: ["<rootDir>/projects"], // Points to all projects
setupFilesAfterEnv: ["<rootDir>/setup-jest.ts"], // Global setup
};
export default config;
import { setupZoneTestEnv } from "jest-preset-angular/setup-env/zone";
setupZoneTestEnv();
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["jest"]
},
"include": ["projects/**/*.spec.ts", "projects/**/*.d.ts"]
}
With this setup, you can run all tests with <leader>tA
.
Known Issue: Backticks in it()
Descriptions
Using backticks (`
) in it()
or describe()
may prevent Neotest from displaying error messages correctly. To avoid this issue, use single ('
) or double ("
) quotes for test descriptions.
Migrating Tests to Jest
If you’re transitioning from other testing frameworks like Jasmine to Jest, refer to the Jest Migration Guide for detailed steps and examples.
Conclusion
By adopting Jest in your Angular 19 monorepo and using Neotest in Neovim, you gain speedier tests, straightforward configuration, and an efficient coding workflow. This approach eliminates the quirks of older testing frameworks, saving you time and effort. Additionally, Neotest’s inline feedback keeps you fully in the zone while coding.