Mastering TDD with TypeScript: A Simple Calculator Example
Introduction
At its core, Test-Driven Development (TDD) works by continuously switching between the test file and the application code file.
You write a failing test in the test file to define the expected behaviour.
It is a Two-Pronged approach between:
It's a tight feedback loop: Test ➜ Fail ➜ Code ➜ Pass ➜ Refactor ➜ Repeat.
Setup
The folder structure for this project comprises of the following:
Using the command ‘mkdir’, create a ‘src’ folder in the root directory, along with a tests folder:
In the tests folder, either by using the command ‘touch’:
touch calculator.tests.ts
Or in vscode, using ‘right click’ and create new file, create a test file called:
calculator.tests.ts
And in the ‘src’ folder, again either using the ‘touch’ command (touch calculator.ts), create a file called:
calculator.ts
𝗜𝗻𝘀𝘁𝗮𝗹𝗹𝗮𝘁𝗶𝗼𝗻 𝗰𝗼𝗺𝗺𝗮𝗻𝗱𝘀:
In a nutshell, these are the ‘npm commands’ you run through to scaffold a basic typescript project:
npm init -y
npm install --save-dev typescript
npx tsc --init
npm install --save-dev jest ts-jest @types/jest
npx ts-jest config:init
𝗪𝗵𝗮𝘁 𝗲𝗮𝗰𝗵 𝗼𝗳 𝘁𝗵𝗲𝗺 𝗱𝗼 𝗮𝗿𝗲 𝗮𝘀 𝗳𝗼𝗹𝗹𝗼𝘄𝘀:
Specifically:
Here’s an example jest.config.js file
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
Breakdown of the above:
preset: 'ts-jest'
Tells Jest to use ts-jest so it can run TypeScript files without manual compilation
testEnvironment: 'node'
Sets the environment for the tests (Node.js in this case). For browser-like environments, you’d use jsdom.
Why Use ts-jest?
❌ TDD Example: Calculator App and Test Setup (Red Phase)
1. In the /src folder, create a file called calculator.ts.
This is our empty app file, where we will define the logic later. For now, it contains a placeholder function with a temporary implementation.
2. In the /tests folder, create a file called calculator.test.ts.
This is our first failing test. It defines what we expect the add function to do. At this point, the test will fail, which is exactly what we want in the Red phase of TDD.
3. Run the test in the VS Code terminal using the command: npx jest
4. You should see the test fail with the following message (or similar):
✅ What Just Happened?
Recommended by LinkedIn
✅ Next Step → Green Phase (Make It Pass!)
Now, you update your app code to pass the test.
In /src/calculator.ts, replace the placeholder with a working function:
Note that we now have the function return a + b; instead of return 0; like before
Next from the VS Code terminal, run the test again:
npx jest
🎉 You’ve successfully done the Red phase of TDD.
🎉 Your test correctly failed and told you exactly what wasn’t working.
🎉 Now you're ready to pass the test and move forward.
✅ Continuing the Red-Green-Refactor Cycle (Calculator Example)
After completing the add() function, you can keep following the TDD process by adding new functionality, like subtraction, multiplication, and division.
❌ Back to Red
We now need to write a failing test again. Seeing as this is a calculator, we now add the functions subtract, multiply and divide:
This is where TypeScript really proves its value. It catches errors at compile time, preventing you from running broken code. In contrast, JavaScript would allow the code to compile and run, only throwing an error at runtime.
So, the test cannot be run, owing to these errors, detailed above.
For the purposes of TDD, and to be able to run the test:
We need to create an empty or minimal version of the function, so TypeScript stops complaining. This lets us run the test, watch it fail (Red), and then we can start writing the code to make it pass (Green).
Below is a minimal version of the calculator.ts file, where each function is defined just enough to satisfy TypeScript and allow you to run the tests, knowing they'll fail at first (which is exactly what you want in TDD’s Red phase).
The errors disappear from the test:
We can now run the tests and expect them to ALL fail. Why ALL you ask?
Because in the calculator.ts each function returns a ‘0’.
It’s a function, but in TDD, during the Red phase, it acts as a stub so you can run failing tests and move forward.
✅ Red ➡️ Green Example: Moving from Stub to Real Function
➡️ Red Phase (Stub Function)
You start with a minimal implementation, a stub that doesn’t do the real work, but makes TypeScript and Jest happy enough to run.
calculator.ts (Red phase)
Now you replace the stub with the real logic that makes the test pass.
✅ Key Takeaway
You start with a stub (minimal placeholder function) to get the test running. Once you see the test fail, you write the real implementation. This progresses from Red ➡️ Green, following TDD.
You write a stub function first to satisfy the compiler and make your test fail (Red), then replace it with the real function to make the test pass (Green).
✅ Green Refactor and pass
We need to update the calculator.ts functions to return values:
Next we run the tests by running the command:
npx jest
in the vscode terminal:
All tests now pass
✅ Refactor Ideas for calculator.ts
1. Inline Simple Functions
If the functions are simple enough, you can make them single-line arrow functions for brevity and readability.
The above compares to what went before below: