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 typescript@5.9.3. 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 typescript@5.9.3
# 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:
How would you like to use ESLint?
- Choose:
To check syntax, find problems, and enforce code style(Use arrow keys and Enter)
- Choose:
What type of modules does your project use?
- Choose:
JavaScript modules (import/export)
- Choose:
Which framework does your project use?
- Choose:
None of these(Since we’re focusing on pure TypeScript here, not React/Vue/Angular specific)
- Choose:
Does your project use TypeScript?
- Choose:
Yes
- Choose:
Where does your code run?
- Choose:
Node(OrBrowserif you’re building for the web; you can select both if needed. For this chapter,Nodeis fine.)
- Choose:
How would you like to define a style for your project?
- Choose:
Use a popular style guide
- Choose:
Which style guide do you want to follow?
- Choose:
Airbnb: https://github.com/airbnb/javascript(A very popular and comprehensive style guide)
- Choose:
What format do you want your config file to be in?
- Choose:
JSON
- Choose:
Would you like to install them now with npm?
- Choose:
Yes
- Choose:
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/parserallows 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 usets-jestfor 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', () => { ... });: Adescribeblock groups related tests together. It makes your test output more organized and readable.it('should correctly add two numbers', () => { ... });: Anit(ortest) 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 theresultis strictly equal to8. 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:
- Add a new function to
src/calculator.tscalledmultiplythat takes two numbers and returns their product. - Write at least two new test cases for the
multiplyfunction insrc/calculator.test.ts. Make sure to cover different scenarios (e.g., positive numbers, zero, negative numbers). - Run your tests to ensure everything passes!
Hint:
- Remember to
exportyour new function fromcalculator.ts. - Import it into
calculator.test.ts. - Use
describeanditblocks, and theexpect().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:
“Any” Usage and Linting:
- Pitfall: Over-relying on the
anytype (@typescript-eslint/no-explicit-anyrule) or using non-null assertions (!) without proper checks. This defeats the purpose of TypeScript’s type safety. - Solution: ESLint, especially with the
@typescript-eslint/recommendedconfig, will warn you aboutany. Strive to be explicit with your types. Useunknownwhen 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 useanytemporarily, add a comment explaining why and create a plan to refactor.
- Pitfall: Over-relying on the
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-prettierandeslint-plugin-prettierare not correctly configured in your.eslintrc.json. Ensureplugin:prettier/recommendedis the last item in yourextendsarray. 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 witheslint-config-prettier).
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.
tsconfig.jsonand Tooling Interaction:- Pitfall: Sometimes, changes in your
tsconfig.json(likebaseUrl,paths, or module resolution options) might confuse ESLint or Jest. - Solution: Ensure
tsconfig.jsonis correctly referenced by your tools.ts-jestautomatically picks it up. For ESLint, the@typescript-eslint/parsercan be configured withprojectproperty inparserOptionsto point to yourtsconfig.jsonif 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.
- Pitfall: Sometimes, changes in your
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
eslintwith@typescript-eslintplugins 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
prettierand, crucially, integrated it with ESLint usingeslint-config-prettierandeslint-plugin-prettierto 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-jestto test our TypeScript functions and wrote our first unit tests usingdescribe,it, andexpectmatchers. - Best Practices: We touched upon the importance of fixing red tests immediately, being mindful of
anyusage, 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!