Setting up a testing suite with jest
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
Install required development dependencies for setting up the testing suite
pnpm i jest @types/jest ts-jest
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'], };
'ts-jest'
: This preset is used to run TypeScript files withJest
. It sets upJest
to work with TypeScript by using thets-jest
transformer, which compiles TypeScript files on the fly during testing.'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.'**/?(*.)+(spec|test).ts'
:**/
: Matches any directory path.?(*.)
: Optionally matches a filename that might contain a dot (.
) before thespec
ortest
keyword.+(spec|test)
: Matches eitherspec
ortest
in the filename..ts
: Ensures that only TypeScript files are matched.Overall: This pattern matches any TypeScript file with
spec
ortest
in its name, located anywhere in the directory structure. Examples of matching filenames includeexample.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.
Now headover to the
package.json
file and you will find atest
command inside your scripts which is simply callsjest
{ "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" } }
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 placeTo 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.
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