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.
Discussion (0)