Welcome back, intrepid TypeScript adventurer! You’ve come a long way, mastering types, interfaces, classes, and even advanced design patterns. But what good is beautifully architected code if it’s riddled with inconsistencies, potential bugs, or simply hard for others to read?

In this crucial chapter, we’re going to dive into the world of Quality Assurance. We’ll equip our TypeScript projects with powerful tools for linting (catching errors and style issues), formatting (ensuring consistent code style), and testing (verifying our code works as expected). These aren’t just “nice-to-haves”; they are absolute necessities for any production-ready application, helping you build robust, maintainable, and collaborative codebases. Get ready to elevate your code quality game!

Before we begin, make sure you have a basic TypeScript project set up, perhaps from a previous chapter. A package.json file and a tsconfig.json are all we really need to get started. If you don’t have one, just create an empty folder, run npm init -y, and then npm install --save-dev [email protected]. Then, run npx tsc --init to get a default tsconfig.json. Easy peasy!


Core Concepts: The Pillars of Code Quality

Let’s understand why these tools are so important before we start integrating them.

What is Linting and Why Do We Need It?

Imagine having a super-smart assistant who reads your code line by line, not to execute it, but to point out potential mistakes, suggest better ways to write things, and ensure you’re following a set of predefined rules. That’s linting!

Linting is the process of statically analyzing your code to flag programmatic errors, stylistic issues, and suspicious constructs. It’s like a spell-checker for your code, but much more powerful.

Why is it important?

  • Early Bug Detection: Catches common errors (like unused variables, unreachable code, or unsafe type assertions) before you even run your application.
  • Code Consistency: Enforces coding standards across your entire project, making it easier for teams to collaborate and understand each other’s code.
  • Improved Readability & Maintainability: Consistent code is easier to read, understand, and maintain over time.
  • Best Practice Enforcement: Guides you towards modern best practices and away from common pitfalls.

For TypeScript and JavaScript, the undisputed champion of linting is ESLint. It’s incredibly configurable and has fantastic support for TypeScript through plugins.

What is Code Formatting and Why is it Important?

Have you ever opened a file written by someone else and it had different indentation, inconsistent use of semicolons, or weird line breaks? It can be jarring and make the code harder to scan.

Code Formatting is about enforcing a consistent visual style for your code. It deals with superficial aspects like:

  • Indentation (spaces vs. tabs, how many spaces)
  • Semicolon usage (always, never, only when needed)
  • Quote styles (single vs. double)
  • Line length
  • Spacing around operators

Why is it important?

  • Readability: A consistent style makes code easier to read and understand at a glance, reducing cognitive load.
  • Reduced Merge Conflicts: When everyone’s editor automatically formats code the same way, you’ll have fewer annoying merge conflicts due to stylistic differences.
  • Focus on Logic: Developers can focus on the logic of the code rather than arguing about trivial style choices.

While ESLint can do some formatting, the dedicated tool for this job, and the industry standard, is Prettier. Prettier is an opinionated code formatter that takes your code and reformats it according to a consistent style.

Why Do We Test Our Code?

Imagine you’ve built a complex function that calculates taxes. You’ve tested it manually a few times, and it seems to work. But then you add a new feature, change something in a different part of the codebase, and suddenly your tax calculation is off. How do you know?

Testing is the process of executing your code to verify that it behaves as expected and meets its requirements. It’s about writing small, isolated pieces of code that “assert” or “expect” certain outcomes from your main application code.

Why is it important?

  • Confidence in Changes: When you refactor or add new features, tests give you confidence that you haven’t broken existing functionality (this is called preventing “regressions”).
  • Early Bug Detection (Runtime): Catches bugs that linting might miss, especially related to business logic or interactions between different parts of your system.
  • Documentation: Tests serve as living documentation, showing how different parts of your code are supposed to be used and what their expected outputs are.
  • Design Feedback: Writing tests often forces you to think about your code’s design, leading to more modular and testable components.

For JavaScript and TypeScript, Jest is an incredibly popular and powerful testing framework, widely adopted for its ease of use and rich feature set.


Step-by-Step Implementation: Building Our Quality Toolkit

Let’s roll up our sleeves and integrate these fantastic tools into our TypeScript project!

1. Setting Up Our Project (Quick Check)

First, let’s ensure we have a basic project structure. Open your terminal in your project directory.

# If you haven't already, let's create a fresh project for this chapter
mkdir ts-quality-chapter
cd ts-quality-chapter

# Initialize a new Node.js project
npm init -y

# Install TypeScript (latest stable as of 2025-12-05 is 5.9.3)
npm install --save-dev [email protected]

# Initialize tsconfig.json with default settings
npx tsc --init

Now, let’s create a simple TypeScript file we can use for demonstration. Create a file src/index.ts and add some code.

// src/index.ts
function greet(name: string): string {
    return `Hello, ${name}!`;
}

console.log(greet("TypeScript Developer"));

let unUsedVariable = 10; // This will trigger a linting error soon!

Great! Our project is ready.

2. Integrating ESLint for Linting

Let’s bring in ESLint to help us catch errors and enforce style.

Installation

We need ESLint itself, plus a parser and plugin specifically for TypeScript.

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
  • eslint: The core ESLint library.
  • @typescript-eslint/parser: A parser that allows ESLint to understand TypeScript syntax.
  • @typescript-eslint/eslint-plugin: A plugin containing TypeScript-specific linting rules.

Initialization

Now, let’s initialize ESLint. This will guide us through creating a configuration file.

npx eslint --init

You’ll be prompted with a series of questions. Let’s walk through them:

  1. How would you like to use ESLint?

    • Choose: To check syntax, find problems, and enforce code style (Use arrow keys and Enter)
  2. What type of modules does your project use?

    • Choose: JavaScript modules (import/export)
  3. Which framework does your project use?

    • Choose: None of these (Since we’re focusing on pure TypeScript here, not React/Vue/Angular specific)
  4. Does your project use TypeScript?

    • Choose: Yes
  5. Where does your code run?

    • Choose: Node (Or Browser if you’re building for the web; you can select both if needed. For this chapter, Node is fine.)
  6. How would you like to define a style for your project?

    • Choose: Use a popular style guide
  7. Which style guide do you want to follow?

    • Choose: Airbnb: https://github.com/airbnb/javascript (A very popular and comprehensive style guide)
  8. What format do you want your config file to be in?

    • Choose: JSON
  9. Would you like to install them now with npm?

    • Choose: Yes

After this, ESLint will install a few more dependencies for the Airbnb style guide and create a new file: .eslintrc.json.

Understanding .eslintrc.json

Open the newly created .eslintrc.json file. It should look something like this (exact rules might vary slightly based on updates to the style guide):

// .eslintrc.json
{
    "env": {
        "es2021": true,
        "node": true
    },
    "extends": [
        "airbnb-base",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "@typescript-eslint"
    ],
    "rules": {
        // You can override or add custom rules here
        // For example, if Airbnb's max-len is too strict for you:
        // "max-len": ["error", { "code": 120, "ignoreComments": true }],
    }
}

Let’s break down the important parts:

  • env: Specifies the environments your code runs in, making global variables from those environments available (e.g., console, process).
  • extends: This is where we tell ESLint to use existing configurations.
    • airbnb-base: Applies the Airbnb JavaScript style guide.
    • plugin:@typescript-eslint/recommended: Applies recommended TypeScript-specific rules from the plugin.
  • parser: Specifies the parser ESLint should use. @typescript-eslint/parser allows it to parse TypeScript code.
  • parserOptions: Configures the parser, specifying the ECMAScript version and module type.
  • plugins: Declares the ESLint plugins we’re using.
  • rules: This is where you can customize, override, or add specific linting rules. Each rule can be set to "off", "warn", or "error".

Running ESLint

Let’s add a script to our package.json to easily run ESLint. Open package.json and add a lint script under "scripts":

// package.json
{
  "name": "ts-quality-chapter",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "lint": "eslint \"{src,apps,libs}/**/*.ts\""  // Add this line
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^7.0.0", // Your version might be slightly different
    "@typescript-eslint/parser": "^7.0.0",       // Your version might be slightly different
    "eslint": "^8.0.0",                          // Your version might be slightly different
    "eslint-config-airbnb-base": "^15.0.0",      // Your version might be slightly different
    "eslint-plugin-import": "^2.0.0",            // Your version might be slightly different
    "typescript": "5.9.3"
  }
}

The lint script tells ESLint to check all .ts files within src, apps, or libs directories.

Now, run the linter:

npm run lint

You should see some errors! Something like:

/path/to/ts-quality-chapter/src/index.ts
  7:5  error  'unUsedVariable' is assigned a value but never used  @typescript-eslint/no-unused-vars
  1:1  error  Expected a newline after the last import/require  import/newline-after-import

See? ESLint immediately caught our unUsedVariable and also suggested a newline after any imports (though we don’t have any in this file yet, it’s a general rule from Airbnb).

Let’s fix the unUsedVariable error by removing the variable:

// src/index.ts
function greet(name: string): string {
    return `Hello, ${name}!`;
}

console.log(greet("TypeScript Developer"));

// let unUsedVariable = 10; // Removed this line!

Run npm run lint again. The no-unused-vars error should be gone! You might still have other stylistic errors depending on your ESLint configuration. This is the power of linting!

3. Integrating Prettier for Formatting

Now, let’s bring in Prettier to automatically format our code.

Installation

npm install --save-dev prettier

Configuration

Prettier is “opinionated,” meaning it has sensible defaults. However, you can customize it with a configuration file. Create a file named .prettierrc.json in your project root:

// .prettierrc.json
{
    "semi": true,
    "singleQuote": true,
    "tabWidth": 4,
    "printWidth": 100,
    "trailingComma": "all"
}
  • semi: Add semicolons at the end of statements (true).
  • singleQuote: Use single quotes instead of double quotes (true).
  • tabWidth: Indent with 4 spaces (default is 2).
  • printWidth: Wrap lines that exceed 100 characters.
  • trailingComma: Add trailing commas wherever valid in ES5 (objects, arrays, function params).

You can also tell Prettier to ignore certain files or directories. Create a .prettierignore file:

# .prettierignore
node_modules/
dist/
build/
coverage/

Running Prettier

Let’s add a script to package.json for Prettier. We’ll add two: format to check, and format:fix to actually apply formatting.

// package.json
{
  "name": "ts-quality-chapter",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "lint": "eslint \"{src,apps,libs}/**/*.ts\"",
    "format": "prettier --check \"{src,apps,libs}/**/*.ts\"",         // Checks if files need formatting
    "format:fix": "prettier --write \"{src,apps,libs}/**/*.ts\""     // Formats files
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^7.0.0",
    "@typescript-eslint/parser": "^7.0.0",
    "eslint": "^8.0.0",
    "eslint-config-airbnb-base": "^15.0.0",
    "eslint-plugin-import": "^2.0.0",
    "prettier": "^3.0.0", // Your version might be slightly different
    "typescript": "5.9.3"
  }
}

Now, let’s make src/index.ts a bit messy to see Prettier in action. Modify src/index.ts to look like this (don’t worry about the style, Prettier will fix it!):

// src/index.ts - Messy version
function  greet ( name : string ) : string {
 return `Hello, ${name}!`
}

console.log(greet("TypeScript Developer"))

Notice the inconsistent spacing, lack of semicolons, and double quotes in the template literal.

First, check if it needs formatting:

npm run format

It should report that src/index.ts needs reformatting.

Now, let Prettier fix it:

npm run format:fix

Open src/index.ts. It should now be beautifully formatted according to our .prettierrc.json rules:

// src/index.ts - Prettier fixed version
function greet(name: string): string {
    return `Hello, ${name}!`;
}

console.log(greet("TypeScript Developer"));

Much cleaner! Notice the tabWidth of 4 spaces, the semicolon, and the consistent spacing.

Making ESLint and Prettier Play Nice

This is CRUCIAL! Without proper integration, ESLint and Prettier will fight each other. ESLint might complain about formatting issues that Prettier wants to fix, leading to frustrating conflicts.

We need two packages:

  • eslint-config-prettier: Turns off all ESLint rules that are unnecessary or might conflict with Prettier.
  • eslint-plugin-prettier: Runs Prettier as an ESLint rule, reporting differences as ESLint issues.
npm install --save-dev eslint-config-prettier eslint-plugin-prettier

Now, update your .eslintrc.json to include these. The prettier configuration should always be the last one in the extends array to ensure it overrides any conflicting rules from previous configurations.

// .eslintrc.json
{
    "env": {
        "es2021": true,
        "node": true
    },
    "extends": [
        "airbnb-base",
        "plugin:@typescript-eslint/recommended",
        "plugin:prettier/recommended" // Add this line LAST
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "@typescript-eslint",
        "prettier" // Add this line
    ],
    "rules": {
        // Any custom rules or overrides go here
        // If you want ESLint to report Prettier issues as errors:
        "prettier/prettier": "error"
    }
}

Now, when you run npm run lint, ESLint will also check for Prettier formatting issues. If you run npm run format:fix first, then npm run lint should report no formatting errors!

4. Integrating Jest for Testing

Finally, let’s set up Jest to write and run tests for our TypeScript code.

Installation

We need Jest, ts-jest (to make Jest understand TypeScript), and @types/jest (for TypeScript type definitions for Jest).

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

Configuration

ts-jest provides a utility to initialize its configuration.

npx ts-jest config:init

This command will create a jest.config.js file in your project root. It will look something like this:

// jest.config.js
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
  • preset: 'ts-jest': Tells Jest to use ts-jest for handling TypeScript files.
  • testEnvironment: 'node': Specifies that tests should run in a Node.js environment (you might use 'jsdom' for browser-based tests).

Writing Our First Test

Let’s create a simple function to test. Create a new file src/calculator.ts:

// src/calculator.ts
export function add(a: number, b: number): number {
    return a + b;
}

export function subtract(a: number, b: number): number {
    return a - b;
}

Now, let’s write a test file for calculator.ts. It’s a common convention to place test files next to the source files, with a .test.ts or .spec.ts suffix. Create src/calculator.test.ts:

// src/calculator.test.ts
import { add, subtract } from './calculator';

describe('Calculator functions', () => {
    it('should correctly add two numbers', () => {
        // Arrange (set up any necessary data)
        const num1 = 5;
        const num2 = 3;

        // Act (call the function we are testing)
        const result = add(num1, num2);

        // Assert (check if the result is what we expect)
        expect(result).toBe(8);
    });

    it('should correctly subtract two numbers', () => {
        const num1 = 10;
        const num2 = 4;
        const result = subtract(num1, num2);
        expect(result).toBe(6);
    });

    it('should handle negative numbers in addition', () => {
        const result = add(-1, -5);
        expect(result).toBe(-6);
    });
});

Let’s break down this test file:

  • import { add, subtract } from './calculator';: We import the functions we want to test.
  • describe('Calculator functions', () => { ... });: A describe block groups related tests together. It makes your test output more organized and readable.
  • it('should correctly add two numbers', () => { ... });: An it (or test) block defines a single test case. Its description should clearly state what it’s testing.
  • expect(result).toBe(8);: This is an “assertion.”
    • expect(): Takes the value you want to test.
    • .toBe(): This is a “matcher” provided by Jest. It checks if the result is strictly equal to 8. Jest has many matchers like .toEqual(), .toContain(), .toBeTruthy(), .toThrow(), etc.

Running Tests

Add a test script to your package.json:

// package.json
{
  "name": "ts-quality-chapter",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest",                                     // Add this line
    "lint": "eslint \"{src,apps,libs}/**/*.ts\"",
    "format": "prettier --check \"{src,apps,libs}/**/*.ts\"",
    "format:fix": "prettier --write \"{src,apps,libs}/**/*.ts\""
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/jest": "^29.0.0", // Your version might be slightly different
    "@typescript-eslint/eslint-plugin": "^7.0.0",
    "@typescript-eslint/parser": "^7.0.0",
    "eslint": "^8.0.0",
    "eslint-config-airbnb-base": "^15.0.0",
    "eslint-config-prettier": "^9.0.0", // Your version might be slightly different
    "eslint-plugin-import": "^2.0.0",
    "eslint-plugin-prettier": "^5.0.0", // Your version might be slightly different
    "jest": "^29.0.0",                 // Your version might be slightly different
    "prettier": "^3.0.0",
    "ts-jest": "^29.0.0",              // Your version might be slightly different
    "typescript": "5.9.3"
  }
}

Now, run your tests:

npm run test

You should see output similar to this, indicating all tests passed:

PASS  src/calculator.test.ts
  Calculator functions
    ✓ should correctly add two numbers (3ms)
    ✓ should correctly subtract two numbers (1ms)
    ✓ should handle negative numbers in addition (0ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.234 s
Ran all test suites.

Congratulations! You’ve successfully set up linting, formatting, and testing for your TypeScript project. This is a monumental step towards building professional, production-ready applications.


Mini-Challenge: Extend the Calculator!

Now it’s your turn to flex those new testing muscles!

Challenge:

  1. Add a new function to src/calculator.ts called multiply that takes two numbers and returns their product.
  2. Write at least two new test cases for the multiply function in src/calculator.test.ts. Make sure to cover different scenarios (e.g., positive numbers, zero, negative numbers).
  3. Run your tests to ensure everything passes!

Hint:

  • Remember to export your new function from calculator.ts.
  • Import it into calculator.test.ts.
  • Use describe and it blocks, and the expect().toBe() matcher.

What to Observe/Learn: You’ll get a feel for the “red-green-refactor” cycle: seeing a test fail (if you write it before the function), making it pass, and then being able to confidently refactor your code knowing tests will catch regressions.


Common Pitfalls & Troubleshooting

Even with these powerful tools, you might run into a few common snags. Here’s how to navigate them:

  1. “Any” Usage and Linting:

    • Pitfall: Over-relying on the any type (@typescript-eslint/no-explicit-any rule) or using non-null assertions (!) without proper checks. This defeats the purpose of TypeScript’s type safety.
    • Solution: ESLint, especially with the @typescript-eslint/recommended config, will warn you about any. Strive to be explicit with your types. Use unknown when you truly don’t know the type, and then narrow it down with type guards. For non-null assertions, ensure the value cannot be null or undefined at that point in your code. If you must use any temporarily, add a comment explaining why and create a plan to refactor.
  2. ESLint and Prettier Conflicts:

    • Pitfall: ESLint reports formatting errors even after Prettier has run, or Prettier re-formats code in a way ESLint then complains about.
    • Solution: This typically happens if eslint-config-prettier and eslint-plugin-prettier are not correctly configured in your .eslintrc.json. Ensure plugin:prettier/recommended is the last item in your extends array. Also, make sure your Prettier configuration (.prettierrc.json) doesn’t conflict with any non-formatting ESLint rules (e.g., a Prettier rule that forces single quotes while an ESLint rule demands double quotes for specific strings, though this is rare with eslint-config-prettier).
  3. Ignoring Test Failures (Red Tests):

    • Pitfall: A test fails, but you ignore it, disable it, or just assume it’s “flaky” without investigating.
    • Solution: A failing test (a “red” test) is a critical warning! It means your code isn’t doing what you expect, or your test is incorrect. Always investigate failing tests immediately. Fix the bug, or fix the test if the expectation was wrong. Never commit code with failing tests.
  4. tsconfig.json and Tooling Interaction:

    • Pitfall: Sometimes, changes in your tsconfig.json (like baseUrl, paths, or module resolution options) might confuse ESLint or Jest.
    • Solution: Ensure tsconfig.json is correctly referenced by your tools. ts-jest automatically picks it up. For ESLint, the @typescript-eslint/parser can be configured with project property in parserOptions to point to your tsconfig.json if you’re using advanced features like type-aware linting. For most basic setups, this isn’t strictly necessary but good to know for complex projects.

Summary

Phew! You’ve just equipped your TypeScript project with a powerful arsenal for quality assurance. Let’s recap what we’ve covered:

  • Linting with ESLint: We learned how ESLint statically analyzes our code to catch errors, enforce best practices, and maintain a consistent code style. We set up eslint with @typescript-eslint plugins and the popular Airbnb style guide.
  • Formatting with Prettier: We discovered how Prettier automatically formats our code for consistent style, improving readability and reducing merge conflicts. We configured prettier and, crucially, integrated it with ESLint using eslint-config-prettier and eslint-plugin-prettier to avoid conflicts.
  • Testing with Jest: We explored the importance of writing tests to verify code behavior, prevent regressions, and build confidence. We set up Jest with ts-jest to test our TypeScript functions and wrote our first unit tests using describe, it, and expect matchers.
  • Best Practices: We touched upon the importance of fixing red tests immediately, being mindful of any usage, and ensuring our quality tools play nicely together.

By consistently applying linting, formatting, and testing, you’re not just writing code; you’re building professional-grade software. These practices are cornerstones of modern development and will make your codebases more robust, maintainable, and enjoyable to work with, both for yourself and your team.

What’s Next?

Now that your code is looking great and working flawlessly, it’s time to think about how to share it with the world! In the next chapter, we’ll dive into the exciting world of Deployment and Publishing, learning how to prepare your TypeScript applications for production and potentially publish them as reusable packages. Get ready to go live!