» Make grep CLI App in Node.js » 2. Development » 2.5 Add Tests

Add Tests

There are several testing frameworks available for Node.js that you can use to write and run tests for your JavaScript code. Some popular testing frameworks for Node.js include: Jest, Mocha, Jasmine and etc.

This project uses Jest as it's a delightful JavaScript Testing Framework with a focus on simplicity.

describe is used to group together related test cases within a logical structure.

describe(name: string, fn: () => void): void;

The beforeAll, beforeEach, afterAll, and afterEach functions allow you to set up or tear down resources before or after the test suite or individual test cases. This is useful for tasks like initializing variables, creating temporary files, or cleaning up resources.

expect(...).toEqual(...) is a statement to assert that a value or object is equal to an expected value.

expect(received).toEqual(expected);

Install Jest and its TypeScript support (if not already installed):

npm install --save-dev jest ts-jest @types/jest

Update package.json to use jest as test script and add a jest section:

"scripts": {
    "test": "npx jest",
    ...
},
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "node"
  }
}

test/grep.test.ts:

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { promisify } from 'util';
import { grep, grepRecursive, grepCount, MatchResult } from '../lib/grep';

const mkdtemp = promisify(fs.mkdtemp);
const writeFile = promisify(fs.writeFile);

let tmpDir: string;

beforeAll(async () => {
  // Set up resources before all tests
  tmpDir = await mkdtemp(path.join(os.tmpdir(), 'grep-test-'));
});

afterAll(() => {
  // Teardown resources after all tests
  // For example, you can delete the temporary directory
  fs.rmSync(tmpDir, { recursive: true });
});

beforeEach(() => {
  // Set up resources before each test
  // This can be useful for scenarios where each test needs a clean state
});

afterEach(() => {
  // Teardown resources after each test
});

describe('grep', () => {
  it('should match lines in a file', async () => {
    const filePath = path.join(tmpDir, 'single-file.txt');
    await writeFile(filePath, 'This is an example line\nThis line should not match');

    const pattern = 'example';
    const options = { ignoreCase: false, invertMatch: false };

    const result = await grep(pattern, filePath, options);

    expect(result[filePath]).toEqual([[1, 'This is an example line']]);
  });

  it('should handle invertMatch correctly', async () => {
    const filePath = path.join(tmpDir, 'single-file.txt');
    await writeFile(filePath, 'This is an example line\nThis line should not match');

    const pattern = 'example';
    const options = { ignoreCase: false, invertMatch: true };

    const result = await grep(pattern, filePath, options);

    expect(result[filePath]).toEqual([[2, 'This line should not match']]);
  });
});

describe('grepRecursive', () => {
  it('should match lines in files recursively', async () => {
    const dirPath = path.join(tmpDir, 'nested-directory');
    const filePath1 = path.join(dirPath, 'file1.txt');
    const filePath2 = path.join(dirPath, 'subdir', 'file2.txt');

    await fs.promises.mkdir(path.join(dirPath, 'subdir'), { recursive: true });
    await writeFile(filePath1, 'This is an example line');
    await writeFile(filePath2, 'Another example line');

    const pattern = 'example';
    const options = { ignoreCase: false, invertMatch: false };

    const result = await grepRecursive(pattern, dirPath, options);

    expect(result).toEqual({
      [filePath1]: [[1, 'This is an example line']],
      [filePath2]: [[1, 'Another example line']],
    });
  });

  it('should handle invertMatch correctly in recursive search', async () => {
    const dirPath = path.join(tmpDir, 'nested-directory');
    const filePath1 = path.join(dirPath, 'file1.txt');
    const filePath2 = path.join(dirPath, 'subdir', 'file2.txt');

    await fs.promises.mkdir(path.join(dirPath, 'subdir'), { recursive: true });
    await writeFile(filePath1, 'This is an example line');
    await writeFile(filePath2, 'This line should not match');

    const pattern = 'example';
    const options = { ignoreCase: false, invertMatch: true };

    const result = await grepRecursive(pattern, dirPath, options);

    expect(result).toEqual({
      [filePath1]: [],
      [filePath2]: [[1, 'This line should not match']],
    });
  });
});

describe('grepCount', () => {
  it('should count the total number of matches in the result', () => {
    const result: MatchResult = {
      'file1.txt': [[1, 'Match line 1'], [3, 'Match line 3']],
      'file2.txt': [[2, 'Match line 2']],
    };

    const count = grepCount(result);

    expect(count).toBe(3);
  });
});

Run the tests:

npx jest

# Or
npm test

You should get test results as below:

> lr_grepjs@1.0.0 test
> npx jest

 PASS  test/grep.test.ts
  grep
    ✓ should match lines in a file (10 ms)
    ✓ should handle invertMatch correctly (1 ms)
  grepRecursive
    ✓ should match lines in files recursively (3 ms)
    ✓ should handle invertMatch correctly in recursive search (3 ms)
  grepCount
    ✓ should count the total number of matches in the result (1 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.63 s, estimated 2 s
Ran all test suites.

If there's something wrong, you will get a detailed assertion error messge like this:

> npx jest

 FAIL  test/grep.test.ts
  grep
    ✕ should match lines in a file (10 ms)
    ✓ should handle invertMatch correctly (1 ms)
  grepRecursive
    ✓ should match lines in files recursively (2 ms)
    ✓ should handle invertMatch correctly in recursive search (2 ms)
  grepCount
    ✓ should count the total number of matches in the result (1 ms)

  ● grep › should match lines in a file

    expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 1

      Array [
        Array [
          1,
    -     "This is an example line2",
    +     "This is an example line",
        ],
      ]

      40 |     const result = await grep(pattern, filePath, options);
      41 |
    > 42 |     expect(result[filePath]).toEqual([[1, 'This is an example line2']]);
         |                              ^
      43 |   });
      44 |
      45 |   it('should handle invertMatch correctly', async () => {

      at Object.<anonymous> (test/grep.test.ts:42:30)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 4 passed, 5 total
Snapshots:   0 total
Time:        1.601 s, estimated 2 s
Ran all test suites.

Then, you can fix your implementations based on this error message.