2. Basics of Vitest
Tags: react, vitest, unittest, javascript
Published: 2024-06-20 18:49:44

## Let's start with What is Vitest...

Vitest is a modern unit testing framework designed for blazing-fast performance, especially when working with JavaScript projects. It leverages the power of Vite, a lightning-quick bundler, to streamline the testing process.

**Key Advantages:**
- **Exceptional Speed:** Vitest boasts exceptional execution times for your tests, thanks to its reliance on Vite's efficient bundling mechanisms. This translates to a significantly smoother development experience, as you won't have to wait long for tests to run after making changes to your code.
- **Seamless Integration with Vite:** If you're already using Vite for your project, Vitest integrates flawlessly. It automatically inherits your Vite configuration, including settings for resolving aliases and plugins, eliminating the need for redundant setup. You can also create a dedicated `vitest.config.ts` file for test-specific configurations.
- **Familiarity for Jest Users:** Coming from Jest, a popular testing framework? Vitest offers a similar syntax, making the transition comfortable. You'll likely find yourself writing tests in a familiar way, leveraging features like `expect`, snapshots, and code coverage.
- **Modern JavaScript Support:** Vitest embraces modern JavaScript features out of the box, providing built-in support for ES modules, TypeScript, and JSX. This aligns perfectly with the current web development landscape.
- **Smart Watch Mode:** Inspired by Hot Module Replacement (HMR), Vitest's watch mode is incredibly intelligent. It only re-runs tests that are directly affected by your code changes, significantly reducing the time it takes to get feedback.

## Installing Vitest: Simple Setup and Next.js 14 Considerations

Vitest boasts a refreshingly straightforward installation process. To get started with basic unit testing, all you need is a single command:

```bash
npm install --save-dev vitest
```

```bash
pnpm add -D vitest
```

```bash
yarn add -D vitest
```

This installs Vitest as a development dependency in your project. With that done, you're ready to write your first tests!

However, if you're working with a Next.js 14 application, there are a few additional considerations to ensure smooth integration with Vitest.

```javascript
import { coverageConfigDefaults, defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],
  test: {
    environment: "jsdom",
    globals: true,
    coverage: {
      exclude: [
        "next.config.mjs",
        "middleware.ts",
        "auth.config.ts",
        "tailwind.config.ts",
        "postcss.config.js",
        // other files which dont require unit testing
        ...coverageConfigDefaults.exclude,
      ],
    },
    env: { // execute the command `openssl rand -base64 32` in the terminal and use the returned value here
      AUTH_SECRET: {YOUR_SECRET},
    },
    server: {
      deps: {
        inline: ["next-auth"],
      },
    },
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});
```

Here's a detailed explanation of the above Vitest configuration for Next.js 14:

**Imports:**

- `coverageConfigDefaults, defineConfig` are imported from `vitest/config`. These provide helper functions for configuring Vitest.

- `react` is imported from `@vitejs/plugin-react`. This plugin enables seamless testing of React components within Vitest.

- `path` is imported from the built-in Node.js `path` module. This provides utilities for working with file paths.

**Configuration:**

- **plugins:** This array specifies plugins to be used by Vitest. Currently, only the `react` plugin is included, enabling React component testing.

- **test:** This object configures various aspects of test execution:

    - **environment:** Sets the test environment to "jsdom", which provides a simulated browser environment for running your tests.

    - **globals:** This setting instructs Vitest to automatically make its built-in test utilities (like `describe`, `it`, `test`, `expect`) globally available within your test files. This eliminates the need to explicitly import them, simplifying your test code (also requires some changes in `tsconfig` file as well. It's mentioned below).

    - **coverage:** This object configures code coverage reporting:

        - **exclude:** This array specifies files to be excluded from code coverage calculations. This includes common Next.js configuration files, middleware, and potentially other files you don't need unit testing for. It also merges with default exclusions provided by Vitest.

    - **env:** This object defines environment variables accessible within your tests. You'll need to replace `{YOUR_SECRET}` with an actual secret value (obtained securely, not shown here for security reasons). This is likely used for testing functionalities that rely on environment variables.

    - **server:** This object configures the test server:

        - **deps.inline:** This array specifies dependencies to be inlined by the test server. Currently, it includes "next-auth". Inlining is a process where Vite integrates the module directly into the test bundle instead of relying on external imports. This approach can be beneficial for several reasons:

            - **Compatibility:** It helps handle dependencies that might not strictly follow Node.js ESM (ECMAScript Module) specifications. `next-auth` might be an example of such a dependency. By inlining, Vite ensures compatibility within the test environment.

            - **Performance:** Inlining can sometimes improve test execution speed as the test server doesn't need to resolve and load the dependency separately. Including `next-auth` in the `deps.inline` array suggests that this dependency might be crucial for your tests, potentially because it interacts with functionalities you're testing.

- **resolve:** This object configures how Vitest resolves module imports:

    - **alias:** This object defines an alias // Run the tests  
        run();for the root of your source code. Here, `@` is mapped to the `./src` directory, simplifying imports within your tests.

Also modify the \`tsconfig\` file to include the following like for getting editor autocomplete support

```json
{
  "compilerOptions": {
    // ...other options
    "types": ["vitest/globals"],
  }
}
```

## Writing the very first test case

**Unlocking Geometric Insights: Introducing calculateArea()**

Embark on a journey into the realm of geometric computations with the latest tool, `calculateArea()`. This versatile function empowers you to effortlessly determine the areas of fundamental shapes such as rectangles, squares, and circles. Proceed further into the blog to explore the inner workings of `calculateArea()`, accompanied by a suite of meticulously crafted test cases ensuring its accuracy and reliability.

```typescript
// calculate.ts

// Function to calculate the area of different shapes
export function calculateArea(shape: string, ...args: number[]): number {
  switch (shape) {
    case 'rectangle':
      if (args.length !== 2) {
        throw new Error('Invalid number of arguments for rectangle');
      }
      return args[0] * args[1];
    case 'square':
      if (args.length !== 1) {
        throw new Error('Invalid number of arguments for square');
      }
      return args[0] * args[0];
    case 'circle':
      if (args.length !== 1) {
        throw new Error('Invalid number of arguments for circle');
      }
      return Math.PI * args[0] * args[0];
    default:
      throw new Error('Invalid shape');
  }
}
```

```typescript
// calculate.spec.ts

// Import the functions to be tested
import { describe, it, assert, run } from 'vitest';
import { calculateArea } from './calculate';

// Define meaningful test suite descriptions
describe('Area Calculation for Geometric Shapes', () => {
  // Test Suite 1: Rectangle and Square
  describe('Rectangles and Squares', () => {
    // Test case 1: Rectangle area calculation
    it('should calculate the area of a rectangle', () => {
      assert.equal(calculateArea('rectangle', 3, 4), 12);
    });

    // Test case 2: Square area calculation
    it('should calculate the area of a square', () => {
      assert.equal(calculateArea('square', 5), 25);
    });

    // Test case 3: Rectangle area calculation with negative numbers
    it('should handle negative numbers for rectangle area calculation', () => {
      assert.equal(calculateArea('rectangle', -3, 4), -12);
    });

    // Test case 4: Square area calculation with zero
    it('should calculate the area of a square with zero side length', () => {
      assert.equal(calculateArea('square', 0), 0);
    });
  });

  // Test Suite 2: Circle
  describe('Circles', () => {
    // Test case 1: Circle area calculation
    it('should calculate the area of a circle', () => {
      assert.approximately(calculateArea('circle', 3), 28.27, 0.01);
    });

    // Test case 2: Circle area calculation with larger radius
    it('should calculate the area of a circle with larger radius', () => {
      assert.approximately(calculateArea('circle', 5), 78.54, 0.01);
    });

    // Test case 3: Circle area calculation with negative radius
    it('should throw an error for negative radius in circle area calculation', () => {
      assert.throws(() => calculateArea('circle', -3), 'Invalid number of arguments for circle');
    });

    // Test case 4: Circle area calculation with zero radius
    it('should handle zero radius for circle area calculation', () => {
      assert.equal(calculateArea('circle', 0), 0);
    });
  });
});
```

```bash
npm vitest
```