DEV Community

Sheng-Lin Yang
Sheng-Lin Yang

Posted on

Unit testing for my CLI tool

In my CLI tool, Repo-snapshot which is built using TypeScript and Vite, I decided to implement unit tests to ensure my functions work correctly and handle edge cases. For testing, I chose Vitest along with @vitest/coverage-v8.

Why I Chose These Tools

Vitest: Vitest is a modern unit testing framework specifically designed for Vite projects. It integrates seamlessly with TypeScript, is fast, and supports features like mocking, snapshots, and coverage.

@vitest/coverage-v8: This plugin provides detailed code coverage reporting using V8’s built-in coverage tool. It helped me identify which parts of my code were fully tested and which were not.

I chose these tools because they fit perfectly with the TypeScript + Vite environment of my CLI tool and allowed me to write tests efficiently without extra configuration overhead.

Setting Up Vitest in My Project

Here’s how I set up Vitest and coverage in my project:

  1. Install dependencies:
pnpm add -D vitest @vitest/coverage-v8
Enter fullscreen mode Exit fullscreen mode
  1. Configure Vitest: I created a vitest.config.ts file in my project root:
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    coverage: {
      enabled: true,
      provider: "v8",
      include: ["src/**/*.ts"],
      exclude: ["tests/**", "node_modules/**"],
    },
  },
});
Enter fullscreen mode Exit fullscreen mode
  1. Update TypeScript config: I modified tsconfig.json to include test files and support ES modules for tests:
{
  // Visit https://aka.ms/tsconfig to read more about this file
  "compilerOptions": {
    // File Layout
    "rootDir": "./",
    "outDir": "./dist",

    // Environment Settings
    // See also https://aka.ms/tsconfig/module
    "module": "nodenext",
    "target": "ES2020",
    // For nodejs:
    "types": ["node", "vitest", "vitest/globals"],

    // Other Outputs
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,

    // Stricter Typechecking Options
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,

    // Recommended Options
    "esModuleInterop": true,
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": false,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
  },
  "include": ["src", "tests"],
  // Ignore tests and node_modules
  "exclude": ["node_modules", "dist"]
}
Enter fullscreen mode Exit fullscreen mode
  1. Add test scripts in package.json:
{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest --watch",
    "coverage": "vitest run --coverage"
  }
}
Enter fullscreen mode Exit fullscreen mode

After this setup, I could run all tests with:

pnpm test
Enter fullscreen mode Exit fullscreen mode

Or test a specific file:

pnpm test tests/file-utils.test.ts
Enter fullscreen mode Exit fullscreen mode

Writing test cases for my CLI tool was a learning experience. Some functions were straightforward, but others had tricky edge cases. For example, one function that parsed repository paths could fail if a user entered an unexpected format. I had to think carefully about input validation, which was an “aha!” moment—writing tests made me anticipate situations I hadn’t considered.

There were times I got stuck on mocking parts of the CLI output. Stepping away and revisiting the problem later helped me find simpler, cleaner solutions. This taught me that unit testing isn’t just about catching bugs—it also improves your design and code clarity.

During testing, I uncovered a few interesting scenarios:

  • Projects containing a TOML config file required careful handling in tests to avoid errors.
  • Edge cases for file paths on different operating systems required me to handle both forward slashes (/) and backward slashes (\).

These discoveries proved the value of testing even small utility functions—tests caught potential runtime errors before users did.

Before this project, I had very little hands-on experience with unit testing. Through this process, I learned:

  • Unit testing helps catch bugs early and improves the reliability of your code.
  • Even small functions deserve tests to handle unexpected input or edge cases.
  • Testing encourages better design and foresight in programming.

Going forward, I plan to write unit tests for almost every function or class I contribute to, as it saves time and effort in the long run and makes projects more robust.

Conclusion:

This experience with Vitest showed me that testing is not just an optional step—it is an essential part of software development. I now feel much more confident incorporating tests into my future projects, and I understand how they can prevent subtle bugs and improve overall code quality.

Top comments (0)