Setting up a testing suite with jest

Ronit PandaRonit Panda
3 min read

Hey welcome back to the series, let's now setup a testing suite with jest in our codebase, to test all the classes we have written in the last a few blogs.

if you don't have the codebase yet:https://github.com/rtpa25/dns-server

  1. Install required development dependencies for setting up the testing suite

     pnpm i jest @types/jest ts-jest
    
  2. create a file named jest.config.js with the following contents

     /** @type {import('ts-jest').JestConfigWithTsJest} */
     module.exports = {
         preset: 'ts-jest',
         testEnvironment: 'node',
         testMatch: ['**/?(*.)+(spec|test).ts'],
     };
    
    1. 'ts-jest': This preset is used to run TypeScript files with Jest. It sets up Jest to work with TypeScript by using the ts-jest transformer, which compiles TypeScript files on the fly during testing.

    2. 'node': Specifies that the test environment should be a Node.js environment. This is useful for testing code that interacts with Node.js APIs and doesn't require a browser-like environment.

    3. '**/?(*.)+(spec|test).ts':

      • **/: Matches any directory path.

      • ?(*.): Optionally matches a filename that might contain a dot (.) before the spec or test keyword.

      • +(spec|test): Matches either spec or test in the filename.

      • .ts: Ensures that only TypeScript files are matched.

      • Overall: This pattern matches any TypeScript file with spec or test in its name, located anywhere in the directory structure. Examples of matching filenames include example.spec.ts, example.test.ts, spec.ts, test.ts, etc.

Summary

The jest.config.js file configures Jest to work with TypeScript using ts-jest, sets the test environment to Node.js, and specifies that Jest should look for test files with spec or test in their names. This setup ensures that Jest can correctly run and compile TypeScript tests in a Node.js environment.

  1. Now headover to the package.json file and you will find a test command inside your scripts which is simply calls jest

     {
         "name": "dns-server",
         "version": "1.0.0",
         "description": "This is a demo DNS server using Node.js & Typescript",
         "scripts": {
             "test": "jest",
             "dev:udp": "dotenv -- tsx watch src/udp-server.ts",
             "dev:http": "dotenv -- tsx watch src/http-server.ts",
             "dev": "pnpm run dev:http & pnpm run dev:udp",
             "build": "tsc",
             "start:http": "node dist/http-server.js",
             "start:udp": "node dist/udp-server.js",
             "start": "pnpm run start:http & pnpm run start:udp"
         },
         "keywords": [],
         "author": {
             "name": "Ronit Panda",
             "email": "pandaronit25@gmail.com",
             "url": "https://ronit.dev/"
         },
         "license": "ISC",
         "devDependencies": {
             "@types/express": "^4.17.21",
             "@types/jest": "^29.5.12",
             "@types/node": "^20.14.2",
             "jest": "^29.7.0",
             "ts-jest": "^29.1.5",
             "tsx": "^4.16.0",
             "typescript": "^5.4.5"
         },
         "dependencies": {
             "@upstash/redis": "^1.31.6",
             "dotenv-cli": "^7.4.2",
             "express": "^4.19.2",
             "zod": "^3.23.8"
         }
     }
    
  2. Let's now head over to src/message/message.test.ts which will look like this:

     import { DNSBuilder } from './builder';
     import { dnsParser } from './parser';
     import {
         Bool,
         DNSObject,
         OPCODE,
         QRIndicator,
         RCode,
         RECORD_TYPE,
     } from './types';
    
     test('check full builder and parser flow', () => {
         const dnsObject: DNSObject = {
             header: {
                 ID: 1234,
                 QR: QRIndicator.QUERY,
                 OPCODE: OPCODE.QUERY,
                 AA: Bool.FALSE,
                 TC: Bool.FALSE,
                 RD: Bool.TRUE,
                 RA: Bool.TRUE,
                 Z: 0,
                 RCODE: RCode.NOERROR,
                 QDCOUNT: 1,
                 ANCOUNT: 0,
                 NSCOUNT: 0,
                 ARCOUNT: 0,
             },
             questions: [
                 {
                     NAME: 'example.com',
                     TYPE: RECORD_TYPE.A,
                     CLASS: 1,
                 },
             ],
         };
    
         const dnsBuilder = new DNSBuilder(dnsObject);
         const buffer = dnsBuilder.toBuffer();
    
         const { questions, answers, header } = dnsParser.parse(buffer);
    
         expect(header.ID).toEqual(dnsObject.header.ID);
         expect(header.QR).toEqual(dnsObject.header.QR);
         expect(header.OPCODE).toEqual(dnsObject.header.OPCODE);
         expect(header.AA).toEqual(dnsObject.header.AA);
         expect(header.TC).toEqual(dnsObject.header.TC);
         expect(header.RD).toEqual(dnsObject.header.RD);
         expect(header.RA).toEqual(dnsObject.header.RA);
         expect(header.Z).toEqual(dnsObject.header.Z);
         expect(header.RCODE).toEqual(dnsObject.header.RCODE);
         expect(header.QDCOUNT).toEqual(dnsObject.header.QDCOUNT);
         expect(header.ANCOUNT).toEqual(dnsObject.header.ANCOUNT);
         expect(header.NSCOUNT).toEqual(dnsObject.header.NSCOUNT);
         expect(header.ARCOUNT).toEqual(dnsObject.header.ARCOUNT);
    
         expect(questions).toHaveLength(1);
         expect(questions[0]).toBeDefined();
         expect(questions[0]?.NAME).toEqual('example.com');
         expect(questions[0]?.TYPE).toEqual(RECORD_TYPE.A);
         expect(questions[0]?.CLASS).toEqual(1);
    
         expect(answers).toHaveLength(0);
     });
    

    If you read it carefully the test is quite simple actually, all it does is creates a dns object with the DNSObject that we built in blog-2
    Further it converts the same object into buffer data with our builder
    After which it parses the built buffer by the builder
    and expects the parsed response to be same as the object we started with in the first place

  3. To run this test simply run:

     pnpm run test
    

if the test passes means our classes are working just fine,
& trust me they will ๐Ÿ˜Ž

Now let's move to the next blog. Since we are done with parsing, building, and protocol-level work, let's start building the actual UDP server that will resolve DNS requests.

0
Subscribe to my newsletter

Read articles from Ronit Panda directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ronit Panda
Ronit Panda

Founding full stack engineer at dimension.dev