Test-Driven Development with Vitest: How to
A code kata is an exercise in programming which helps a programmer hone their skills through practice and repetition.
The term was probably first coined by Dave Thomas, co-author of the book The Pragmatic Programmer, in a bow to the Japanese concept of kata in the martial arts.
Katas
- Hello World
- FizzBuzz
- Rock Paper Scissors
- Primes
Setup
All project dependencies are installed and managed via npm, the Node.js package manager.
npm install
npm test
Continuous Integration with Github Actions workflows
A GitHub Actions workflow is essentially a customizable automated process that can perform various tasks. You set it up using a YAML file in your repository, and it kicks in based on events in your repository, manual triggers, or on a schedule you define.
These workflows live in the .github/workflows
directory of your repository, and you can have multiple workflows, each handling different tasks.
This is just an example:
name: Node CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: Project setup `npm ci`
run: npm ci
- name: Run `test`
run: npm test
Test-driven development
Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle:
- first the developer writes an (initially failing) automated test case that defines a desired improvement or new function.
- then produces the minimum amount of code to pass that test.
- finally refactors the new code to acceptable standards.
Toolkit
Vitest is a next-generation unit testing framework built on Vite, offering lightning-fast test execution, seamless migration from Jest due to its compatible features, intelligent watch mode for efficient development, and out-of-the-box support for modern JavaScript like ES modules, TypeScript, and JSX.
Istanbul is a JavaScript code coverage tool that helps you understand how well your tests exercise your codebase. It works by instrumenting your code to track which parts are executed during testing, providing insights into areas that might need more attention.
Stryker is a tool for mutation testing, specifically designed for JavaScript and similar languages. It injects tiny faults (mutations) into your code, then re-runs your tests. If a mutation breaks a test, it indicates your tests are effective at catching errors. This helps improve the quality and reliability of your codebase.
Example
Following an example of Test-Driven Development using Vitest for the most famous application: Hello World
!
Setup is easy, just run an npm
command and change few lines into your package.json
.
npm install -D vitest
"scripts": {
"test": "vitest"
},
Now you are able to run unit tests with npm test
.
First of all we should create a new file HelloWorld.spec.js
.
Now we can start writing our first test.
//- HelloWorldSpec.js
import { describe, it, expect } from 'vitest';
import HelloWorld from from './HelloWorld';
describe('HelloWorld', () => {
it('should exist.', () => {
// given
new HelloWorld();
});
});
β RED - Try running test and it will fail.
Create a new file HelloWorld.js
.
The next step is writing some code that would cause the test to pass.
//- HelloWorld.js
function HelloWorld() {
}
export default HelloWorld;
π GREEN - Try running test and it will pass.
β Need for refactoring?
We have a green bar! Now we can write a new test.
//- HelloWorld.spec.js
...
it('should greet() correctly.', () => {
// given
const helloWorld = new HelloWorld();
// then
expect(helloWorld.greet()).to.equal('Hello world');
});
...
β RED - Try running test and it will fail.
Now we can write some code that would cause the test to pass.
//- HelloWorld.js
...
HelloWorld.prototype.greet = function () {
return 'Hello world';
};
π GREEN - Try running test and it will pass.
β Need for refactoring?
π Done
SPEC - HelloWorld.spec.js
//- HelloWorld.spec.js
import { describe, it, expect } from 'vitest';
import HelloWorld from from './HelloWorld';
describe('HelloWorld', () => {
it('should exist.', () => {
// given
new HelloWorld();
});
it('should greet() correctly.', () => {
// given
const helloWorld = new HelloWorld();
// then
expect(helloWorld.greet()).to.equal('Hello world');
});
});
SRC - HelloWorld.js
//- HelloWorld.js
function HelloWorld() {
}
HelloWorld.prototype.greet = function () {
return 'Hello world';
};
export default HelloWorld;
Now if we decide to refactor the application moving from prototype
to class
, we can do it without fear.
So, letβs do this π
//- HelloWorld.js
export default class {
greet() {
return 'Hello world'
}
}
π GREEN - Try running test and it will pass.
Further readings
- Test Driven Development: By Example (Kent Beck) - Addison-Wesley
- JavaScript Testing with Jasmine (Evan Hahn) - OβReilly Media