Don't like reading? Only want to see code? Here is the github repo :)
Opening
If you've been scratching your head trying to test your new TypeScript Express API - I've been there. And I'd love to save you some time.
I was trying my hand at converting a Node and Express api to use TypeScript. All was going well until I got to testing and I started having all of these existential questions. Like do I need to 'build' my test files?
, do my config files need to be 'built'?
, and why did i decide to use TypeScript when my API already worked!?
.
This article can answer some of those questions. It also assumes you know a little bit about the technologies the project uses (TypeScript, Node, Express, SuperTest, and Jest) - this is more of a project structure guide than an in-depth look at the technologies used.
Initialize project and import the imports
- Create a directory for your project and
cd
into it. - Use NPM to initialize the project
npm init -y
. - Import dependencies
npm i express
. - Import dev-dependencies
npm i --save-dev typescript supertest nodemon jest ts-jest ts-node @types/jest @types/supertest @types/express
.
Initialize TypeScript
Now let's add TypeScript to our project.
npx tsc --init
The above command will generate a tsconfig.json
file.
You'll want to modify it with the below. Not every item is necessary, feel free to further configure it to match your needs.
A quick note on the exclude
value, these are files that the build will ignore. Not all of them exist yet ;)
{
"exclude": ["./coverage", "./dist", "__tests__", "jest.config.js"],
"ts-node": {
"transpileOnly": true,
"files": true
},
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"rootDir": "./src",
"moduleResolution": "node",
"checkJs": true,
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true
}
}
Initialize Jest
Up next, we want to add the Jest testing framework to our project.
npx ts-jest config:init
The above command will generate a jest.config.js
file. You'll want to modify it with the below, so it works with ts-jest
(this is what makes jest work with TypeScript).
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
};
Create a basic Express app with TypeScript
We'll need to create a src
directory with two TypeScript files in it: app.ts
and server.ts
. In the src
directory, we want to add another directory: routes
. In the routes
directory we want to add a user.routes.ts
file.
app.ts
import express, { Application, Request, Response, NextFunction } from "express";
import { router as userRoutes } from "./routes/user.routes";
const app: Application = express();
app.use("/users", userRoutes);
app.use("/", (req: Request, res: Response, next: NextFunction): void => {
res.json({ message: "Allo! Catch-all route." });
});
export default app;
server.ts
import app from "./app";
const PORT: Number = 5050;
app.listen(PORT, (): void => console.log(`running on port ${PORT}`));
user.routes.ts
import { Router, Request, Response } from "express";
const router = Router();
router.get("/", (req: Request, res: Response): void => {
let users = ["Goon", "Tsuki", "Joe"];
res.status(200).send(users);
});
export { router };
Configure package.json
Let's configure our package.json
to use our new tools! To the scripts
section add the following:
scripts: {
"test": "jest --coverage",
"dev": "nodemon ./src/server.ts",
"build": "tsc"
}
Making sure our API is working
Now let's be sure we haven't made any mistakes so far. Run the command npm run dev
. Open a browser and go to http://localhost:5050/
. You should be greeted with the welcome message we defined on line 10 of app.js Allo! Catch-all route.
. Now try out our user route http://localhost:5050/users
, where you should find a list of our users from user.routes.ts ["Goon", "Tsuki", "Joe"]
.
Writing our tests
Now for the moment you've been waiting for... testing.
in our project add a __tests__
directory. In that directory we'll duplicate the file structure we made in the src
directory. Creating a app.test.ts
, server.test.ts
, and routes/user.routes.test.ts
.
.
Let's write our first test, just to make sure jest is working.
server.test.ts
describe("Server.ts tests", () => {
test("Math test", () => {
expect(2 + 2).toBe(4);
});
});
Now we'll us SuperTest to make a network request test.
app.test.ts
import request from "supertest";
import app from "../src/app";
describe("Test app.ts", () => {
test("Catch-all route", async () => {
const res = await request(app).get("/");
expect(res.body).toEqual({ message: "Allo! Catch-all route." });
});
});
Now our last test will test our users
route.
user.routes.test.ts
import request from "supertest";
import app from "../../src/app";
describe("User routes", () => {
test("Get all users", async () => {
const res = await request(app).get("/users");
expect(res.body).toEqual(["Goon", "Tsuki", "Joe"]);
});
});
Add a .gitignore
Now as a git cleanliness note, create a .gitignore
file.
In there we can add some files that we want git to ignore:
node_modules
coverage
jest.config.js
dist
Closing
Setting up testing in a TypeScript/Express API took me a considerable amount of time. And I was really surprised how few resources I found. I hope this helps you in any TypeScript testing predicament you might find your self in.
I'm not a TypeScript authority, I'm just happy I was able to get this working. So if you have notes on what your own setup is like, or advice on making this setup better - feel free to reach out or comment :)
If you liked the article or want to see more of my work, feel free to check out my portfolio and GitHub.
Top comments (7)
Thanks for sharing. I wander why duplicate the file structure in the src directory to test directory?
@sayandcode is totally right, where you put the test files is up to you.
Some people like to make a directory for each component where they keep the component, css, and test files. I think there is a good argument for keeping files and tests next to each other as it forces developers to remember that they have tests and should keep the updated! Having tests in their own directory can lead to them being overlooked and neglected.
I think you can also just put the test side by side to the actual file if you want. Then we can remove all references to test
In my experience, I've come across both approaches when it comes to organizing test files. Some people prefer to keep them in the same location as the code, using file name patterns like ".spec." or ".test.", while others prefer to keep them in a separate folder. It really depends on personal preference and what works best for the project.
Thank you for this! I was shying away from using Jest thinking it would be hard to setup in my existing project. You just showed me how to do it so simply!
Wow, I really appreciate the simplicity of the settings and code you've shared! I just upvoted and starred it on GitHub. Keep up the great work of sharing your knowledge and skills with others!
Ah thank you so much for writing this. Almost gave up on writing tests.