Restful API End-To-End (E2E) Testing
Published: 2023-10-24 07:14:49

by [Fredrick Emmanuel](https://blog.openreplay.com/authors/fredrick-emmanuel)

<blockquote><em>
End-to-end testing focuses on testing the entire application flow to ensure it works as designed. Testing an express route helps catch any errors that might occur during the development stage, boosting productivity. This tutorial will help you develop your first RESTful API E2E test.
</em></blockquote>

<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>

End-to-end testing, often called E2E or system testing, is a software testing technique that ensures the application works as designed. It runs the application from start to finish, checking for any flaws in the development stage before moving to deployment. E2E testing ensures that the application meets business requirements and users’ expectations.

Unit testing is also a testing technique done in the development stage, but it involves the testing of individual components of an application. 
These various methodologies have their benefits and drawbacks. E2E testing differs from Unit testing, which focuses on the smallest units of code, mostly functions or methods. A unit test is better at catching errors because it isolates each component compared to E2E, which tests the application as a whole. E2E, on the other hand, ensures that the application is built as intended, meeting users’ expectations. 

You can decide whether to pick any of the testing techniques or explore [more options](https://smartbear.com/learn/automated-testing/software-testing-methodologies/) like [Integration testing](https://www.simplilearn.com/what-is-integration-testing-examples-challenges-approaches-article).

## Importance of testing in software development
The importance of software testing cannot be overemphasized, as it offers vast benefits. The list below contains just a few:

- **Quality Assurance:** Testing an app thoroughly confirms that the app follows the outlined requirements and satisfies customers. It verifies if the application is of a high standard and ensures that it rivals other existing competitors. 
- **Detecting and Fixing Bugs:** Examining the entire application using any technique can reveal and rectify errors in the staging phase. This makes testing very cost-effective and prevents users from using defective products.
- **Security:** Top-notch security is desired in any application. Testing helps identify various vulnerabilities and lapses that might be found in the software. This assures users that their credentials are safe and well-protected.

The list can go on and on. This shows how testing is a very crucial part of developing software. With this knowledge, we can build an Express application and test it to see if it works as proposed.

Enough with the theory part. Let’s get our hands on some code. We will develop a payment API using Express and MongoDB as the database.

## Getting Started

In this tutorial, we will create an API that will allow users to:
- Create an account.
- Deposit money into their account and
- Send money from their account to another user.

To demonstrate how API testing works, the money that will be used will be virtual, as we will not use any payment platform.

### Installing dependencies
To get started, we need to install the needed dependencies. Create an empty folder called `apitest`; you can name the folder as you please. Next, open this folder in your terminal and run the following:
```bash
yarn init --yes
yarn add express mongodb bcryptjs mongoose dotenv
yarn add nodemon jest @types/express @types/node @types/mongoose @types/supertest @types/jest supertest ts-jest ts-node typescript -D
```
Based on the above code, we created a `package.json` file with default content using `yarn init --yes`, then we installed the various [dependencies](https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add) and [devDependencies](https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-dev-d).
Next, we will initialize the default config for typescript using the code below:
```bash
npx tsc --init
```
This will create a new configuration file with:
```
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
```
Modify the **package.json file** by replacing the script section with the one below.
```bash
    "scripts": {
       "start": "ts-node src/server.ts",
       "dev": "nodemon --exec ts-node src/server.ts",
       "test": "npx jest --detectOpenHandles --forceExit"
   },
```

### Setting up the Database
Once we have installed the required dependencies, we will set up [MongoDB Atlas](https://www.mongodb.com/atlas/database) next.
Navigate to Atlas’ webpage and [register](https://www.mongodb.com/cloud/atlas/register) if you don't have an account or [login](https://account.mongodb.com/account/login?nds=true&_ga=2.150727030.1365950155.1690311813-1766894272.1685713041) if you are an existing user.

On your dashboard, create a new database:
![Build a database](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1690452500467_build+a+database.png)

On the next screen, you will be asked to choose a plan. Select **MO Free** and click on the **Create** button. You can choose to select any other plan of your choice.
![Choose a database](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1690454365741_select+free.png)

Enter the user username and password of your choice and click on **Create User**.
![Creating an admin](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1690454637328_create+admin+user.png)

On the left nav bar, select **Network Access** and click on **ADD IP ADDRESS**
![Network Access](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1690456011189_add+ip+address.png)

You can either choose to access the Atlas database from any device, or you can specify the IP Address of your desired device. Once you’ve added an **IP ADDRESS**, click on the **Confirm** button.
![Add an IP address](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1690455653485_access+from+anywhere.png)

### Connecting the application to the database
After we’ve set up the database, the next step is to connect the application to MongoDB.
There are [various](https://github.com/mongodb-university/atlas_starter_nodejs) methods to do this, but we will make use of [**Mongoose**](https://mongoosejs.com/).

Select **Database** by the side nav bar on your Atlas dashboard. Click on the **Connect** button.
![Connect button](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1690467687370_connect+button.png)

Click on **Drivers.**
![Drivers](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1690467894774_drivers.png)

Scroll to step 3 and copy the **connection string**
![connection string](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1690981582369_connection+string.png)

Next, open the **apitest** folder in Visual Studio Code, create a file called **.env** in the root folder and add the **connection string** as shown below:
```
#./.env
MONGO_URI=mongodb+srv://yourUserame:yourPassword@clusterName.setOfStrings.mongodb.net/?retryWrites=true&w=majority
#PORT: 5000
PORT=5000
```
Replace `yourUsername` and `yourPassword` with your specified username and password, respectively.
Following the addition of the connection string to the **.env** file,
1. Create a folder called **src** in the root folder.
2. Create a file called **server.ts** and **app.ts** in it**.**
3. Add the following code to the **app.ts** file.
```typescript
// src/app.ts
import express from 'express';
   
const app = express();
app.use(express.json());
   
//Routes
app.use('/api/signup', reqire('./routes/signup'));
app.use('/api/login', require('./routes/login'));
app.use('/api/transfer', require('./routes/transfer'));
app.use('/api/deposit', require('./routes/deposit'));
app.use('/api/find', require('./routes/find'));
   
export default app;
```
4. Add the code below to the **server.ts file.**
```typescript
// src/server.ts
import app from './app';
import dotenv from 'dotenv';
dotenv.config();

import { connectDB, testDB } from './config/db'; //Importing the connectDB function
connectDB().catch(err => {
 console.log(err.message);
}); //Calling the connectDB function
const PORT = (process.env.PORT || 5000) as number;

app.listen(PORT, () => {
 console.log('Server is listening on port ' + PORT);
});
```
Above, we created an express server that runs on the port received from the **.env** file or on port `5000`**.**
Once we’re done setting up the server, the next step is to connect to MongoDB.
To do this, create a folder in the **src** folder called **config** and a file in the **config** called  **db.ts.**
Add the code below to the **db.ts** file once you have created it.
```typescript
// src/config/db.ts
import { connect } from 'mongoose';

const MONGO_URI = process.env.MONGO_URI as string; //Getting the URI from .env file

export async function connectDB() {
 await connect(MONGO_URI); //Connecting to the database
 console.log('Database is connected successfully');
}
```
Next, import the `connectDB` function from the **db.ts** file into the **server.ts** file.
```typescript
// src/server.ts
import express from 'express';

import dotenv from 'dotenv';
dotenv.config();

import { connectDB } from './config/db'; //Importing the connectDB function
connectDB().catch(err => {
 console.log(err.message);
}); //Calling the connectDB function

const app = express();
const PORT = (process.env.PORT || 5000) as number;

app.listen(PORT, () => {
 console.log('Server is listening on port ' + PORT);
});
```
After connecting to the database, we will create the account schema. In this schema, we will create a blueprint for the Mongo database containing:

- Username,
- Password, and
- Account balance.

Create a model in the **src** folder called **model** and create a file in it, **Account.ts.**
Add the following to the **Account.ts** file:
```typescript
import { Schema, model } from 'mongoose';

interface IAccount {
 //Interface for the account schema
 username: string;
 password: string;
 balance: number;
}

const accountSchema = new Schema<IAccount>({
 //Creating the account schema
 username: {
   type: String,
   required: true,
   unique: true,
 },
 password: {
   type: String,
   required: true,
 },
 balance: {
   type: Number,
   minLength: 8;
   required: true,
 },
});

const Account = model<IAccount>('Account', accountSchema); //Creating the account model

export default Account; //Exporting the account model
```
## Creating various express routes.

In this section, we will set up different routes for the express application.
These routes include:

- POST `/api/signup`
- POST `/api/login`
- PUT `/api/deposit`
- PUT `/api/transfer`
- GET `api/find/:username`

We will create a boiler template that will handle the routes' functions.
Create a folder called **Controller,** create a file called **index.ts** in it, and add the code below to the file.

```typescript
import { Request, Response } from 'express';

export const deposit = async (req: Request, res: Response) => {};
export const find = async (req: Request, res: Response) => {};
export const transfer = async (req: Request, res: Response) => {};
export const login = async (req: Request, res: Response) => {};
export const signup = async (req: Request, res: Response) => {};
```

Next, create a folder, **routes,** and add the following files and their respective codes to it.

- **signup.ts:**

```typescript
// src/routes/signup.ts
import express, { Request, Response } from 'express';
import { signup } from '../Controller';
const router = express.Router();

router.route('/').post(signup);

router.route('*').all((req, res) => {
 res.status(404).json({ message: 'Invalid route' });
});

module.exports = router;
```

- **login.ts:**

```typescript
// src/routes/login.ts
import express, { Request, Response } from 'express';
import { login } from '../Controller';
const router = express.Router();

router.route('/').post(login);

router.route('*').all((req, res) => {
 res.status(404).json({ message: 'Invalid route' });
});

module.exports = router;
```

- **deposit.ts:**

```typescript
// src/routes\deposit.ts
import express, { Request, Response } from 'express';
import { deposit } from '../Controller';
const router = express.Router();

router.route('/').put(deposit);

router.route('*').all((req, res) => {
 res.status(404).json({ message: 'Invalid route' });
});

module.exports = router;
```

- **transfer.ts:**

```typescript
// src/routes/transfer.ts
import express, { Request, Response } from 'express';
import { transfer } from '../Controller';
const router = express.Router();

router.route('/').put(transfer);

router.route('*').all((req, res) => {
 res.status(404).json({ message: 'Invalid route' });
});

module.exports = router;
```

- **find.ts:**

```typescript
//src/routes/find.ts
import express, { Request, Response } from 'express';
import { find } from '../Controller';
const router = express.Router();

router.route('/:username').get(find);

router.route('*').all((req, res) => {
 res.status(404).json({ message: 'Invalid route' });
});

module.exports = router;
```

Lastly, import these various routes into the **server.ts** file and use them.

```typescript
// src/server.ts

// The rest of the code
const app = express();
app.use(express.json());

//Routes
app.use('/api/signup', require('./routes/signup'));
app.use('/api/login', require('./routes/login'));
app.use('/api/transfer', require('./routes/transfer'));
app.use('/api/deposit', require('./routes/deposit'));
app.use('/api/find', require('./routes/find'));

const PORT = (process.env.PORT || 5000) as number;

app.listen(PORT, () => {
 console.log('Server is listening on port ' + PORT);
});
```

<CTA_Middle_Basics />



## Adding controllers
In this section, we will add the functionalities for the various routes. Open the **index.ts** file in **/src/Controller** and let’s handle the `signup` function.
```typescript
//src/Controller/index.ts

export const signup = async (req: Request, res: Response) => {
 const { username, password }: { username: string; password: string } =
   req.body; //Getting the username and password from the request body

 if (!username || !password) {
   //Checking if the username or password is missing in the request body
   return res
     .status(400)
     .json({ message: 'Username or password not provided' });
 }

 try {
   const lowerUsername = username.toLowerCase(); //Converting the username to lowercase
   const checkUsername: any = await Account.findOne({
     username: lowerUsername
   });
   if (password.length < 8) {
     //Checking if the password is at least eight characters long
     return res
       .status(400)
       .json({ message: 'Password must be at least 8 characters long' });
   }
   if (checkUsername) {
     //Checking if the username already exists
     return res.status(400).json({ message: 'Username already taken' });
   }
   const account = new Account({
     //Creating a new account
     username: lowerUsername,
     password,
     balance: 0
   });
   await account.save(); //Saving the account to the database
   return res.status(201).json({ message: 'Account created' });
 } catch (error: any) {
   console.log(error.message); //Logging the error message if any
   return res.status(500).json({ message: 'Error creating account' });
 }
};
```

Above, we used the username and password that will be passed to the request body to create a new account. For the simplicity of this project, we did not hash out the password. This is not acceptable. All passwords must be hashed first using services like [bcrypt](https://www.npmjs.com/package/bcryptjs) or [crypto](https://www.npmjs.com/package/crypto-js) before storing them in the database. Hashing your password helps to protect its content even when the database is accessed by unauthorized users.

Next, we will handle the `login` function.

```typescript
//src/Controller/index.ts

export const login = async (req: Request, res: Response) => {
 const { username, password }: { username: string; password: string } =
   req.body; //Getting the username and password from the request body

 if (!username || !password) {
   //Checking if the username or password is missing in the request body
   return res
     .status(400)
     .json({ message: 'Username or password not provided' });
 }

 try {
   const lowerUsername = username.toLowerCase(); //Converting the username to lowercase
   const checkUsername = await Account.findOne({ username: lowerUsername });
   if (!checkUsername) {
     //Checking if the username exists in the database
     return res
       .status(400)
       .json({ message: 'Username or password is incorrect' });
   }
   if (checkUsername.password !== password) {
     //Checking if the password is correct
     return res
       .status(400)
       .json({ message: 'Username or password is incorrect' });
   }
   return res.status(200).json({ message: 'Login successful' });
 } catch (error: any) {
   console.log(error.message); //Logging the error message if any
   return res.status(500).json({ message: 'Error logging in' });
 }
};
```

We got the username and password from `req.body` and checked if the password in the database for that user matches the one provided. After handling the signup and login functionality, the `deposit` function comes next.

```typescript
//src/Controller/index.ts

export const deposit = async (req: Request, res: Response) => {
 const {
   username,
   password,
   amount
 }: { username: string; password: string; amount: number } = req.body; //Getting the username, password and amount from the request body

 if (!username || !password || !amount || amount <= 0) {
   //Checking if the username, password or amount is missing in the request body
   return res.status(400).json({ message: 'Missing parameters' });
 }
 try {
   const lowerUsername = username.toLowerCase(); //Converting the username to lowercase
   const checkUsername: any = await Account.findOne({
     username: lowerUsername
   });
   if (!checkUsername) {
     //Checking if the username exists in the database
     return res
       .status(400)
       .json({ message: 'Username or password is incorrect' });
   }
   if (checkUsername.password !== password) {
     //Checking if the password is correct
     return res
       .status(400)
       .json({ message: 'Username or password is incorrect' });
   }
   const newAmount: number = parseInt(amount as any) + checkUsername.balance; //Calculating the new balance
   await Account.updateOne(
     //Updating the balance in the database
     { username: lowerUsername },
     { balance: newAmount }
   );
   return res.status(201).json({ message: 'Deposit successful' });
 } catch (error: any) {
   console.log(error.message); //Logging the error message if any
   return res.status(500).json({ message: 'Error depositing funds' });
 }
};
```

Based on the code above, we performed the login logic and then added the specified amount to the user’s account.
Now, we will handle the `transfer` function.

```typescript
//src/Controller/index.ts

export const transfer = async (req: Request, res: Response) => {
 const {
   username,
   recipient,
   password,
   amount
 }: {
   username: string;
   password: string;
   recipient: string;
   amount: number;
 } = req.body; //Getting the username, password, recipient and amount from the request body

 if (!username || !password || !recipient || !amount || amount <= 0) {
   //Checking if the username, password, recipient or amount is missing in the request body
   return res.status(400).json({ message: 'Missing parameters' });
 }

 try {
   const lowerUsername = username.toLowerCase();
   const lowerrecipient = recipient.toLowerCase(); //Converting the username and recipient to lowercase
   if (lowerUsername === lowerrecipient) {
     //Checking if the username and recipient are the same
     return res.status(400).json({ message: 'Cannot transfer to self' });
   }
   const checkUsername: any = await Account.findOne({
     username: lowerUsername
   });
   if (!checkUsername) {
     //Checking if the username exists in the database
     return res
       .status(400)
       .json({ message: 'Username or password is incorrect' });
   }
   if (checkUsername.password !== password) {
     //Checking if the password is correct
     return res
       .status(400)
       .json({ message: 'Username or password is incorrect' });
   }
   if (checkUsername.balance < amount) {
     //Checking if the user has sufficient funds
     return res.status(400).json({ message: 'Insufficient funds' });
   }
   const checkrecipient: any = await Account.findOne({
     username: lowerrecipient
   });
   if (!checkrecipient) {
     //Checking if the recipient exists in the database
     return res.status(400).json({ message: 'recipient not found' });
   }
   const newAmount: number = checkUsername.balance - parseInt(amount as any); //Calculating the new balance for the user
   await Account.updateOne(
     { username: lowerUsername },
     { balance: newAmount }
   );
   const newAmount2: number = checkrecipient.balance + parseInt(amount as any); //Calculating the new balance for the recipient
   await Account.updateOne(
     { username: lowerrecipient },
     { balance: newAmount2 }
   );
   return res.status(201).json({ message: 'Transfer successful' });
 } catch (error: any) {
   console.log(error.message);
   return res.status(500).json({ message: 'Error transferring funds' });
 }
};
```

In the code above, we also performed the login logic and 
- Ensured that users do not transfer to themselves.
- Ensured that the recipient is in the database.
- Converted the amount passed to an integer and performed the necessary arithmetic operations.
- Updated the user and recipient balance, respectively.

Lastly, we will handle the `find` function.

```typescript
//src/Controller/index.ts

export const find = async (req: Request, res: Response) => {
 const { username }: { username: string } = req.params as any; //Getting the username from the request parameters

 if (!username) {
   //Checking if the username is missing in the request parameters
   return res.status(400).json({ message: 'Missing parameters' });
 }

 try {
   const lowerUsername = username.toLowerCase(); //Converting the username to lowercase
   const checkUsername: any = await Account.findOne({
     username: lowerUsername
   });
   if (!checkUsername) {
     //Checking if the username exists in the database
     return res.status(400).json({ message: 'Username not found' });
   }
   const response = {
     //Creating the response object with the username and balance
     username: checkUsername.username,
     balance: checkUsername.balance
   };
   return res.status(200).json({ user: response });
 } catch (error: any) {
   console.log(error.message); //Logging the error message if any
   return res.status(500).json({ message: 'Error finding user' });
 }
};
```
In the previous functions, we got the username from the request body, but in the `find` function, we got the username from the request parameter. We saved the username and then searched the database for the corresponding user to retrieve their balance.

Feel free to manually test out the route using [Postman](https://www.postman.com/) or [Thunder Client in Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client).

## Testing the express routes

Once all the setups are done, it’s time to dive into the main cause of this article, which is **Testing**.
To start testing the express routes, you need to get a separate database for your testing. It is not ethical to have the same database for your test and primary application.

You can follow the section where we set up the database to create a database for testing.

After you’ve created the database, add the test connection string to the **.env** file.
```
#./.env
   
#MONGO URI
MONGO_URI=mongodb+srv://yourUserame:yourPassword@clusterName.setOfStrings.mongodb.net/?retryWrites=true&w=majority
#TEST MONGO URI
TESTING_MONGO_URI=mongodb+srv://yourUserame:yourPassword@clusterName.setOfStrings.mongodb.net/testing?retryWrites=true&w=majority
#PORT: 5000
PORT=5000
```

Create a function in the **db.ts** file that will connect to the test database.

```typescript
// src/config/db.ts
   
export async function testDB() {
   await connect(TESTING_MONGO_URI);
}
```

Open your terminal and run `npm install @faker-js/faker --save-dev` to install the faker package. [Faker](https://fakerjs.dev/) helps to generate realistic data that is employed in populating the database and in testing.

Next, create a file named **jest.config.js** in the root directory and add the code below to it. Jest makes use of this file to configure the **E2E** testing for the application.

```javascript
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
 preset: 'ts-jest',
 testEnvironment: 'node',
 testTimeout: 10000,
 setupFiles: ['<rootDir>/.jest/setEnvVars.js']
}; 
```

Above, we indicated the duration (10,000 milliseconds) that Jest will pause for a API test to run before proceeding to subsequent tests.
Create a folder in the root directory called **.jest,** create a file named **setEnvVars.js** in it, and add the following:

```javascript
process.env.TESTING_MONGO_URI = 'mongodb+srv://yourUserame:yourPassword@clusterName.setOfStrings.mongodb.net/testing?retryWrites=true&w=majority';
```
Following the setup of Jest, the next step involves creating a file designated for E2E testing in this application. You can choose to name this file as you please, but in this tutorial, it’s called **app.test.ts.**

Add the following to it:

```typescript
// ./app.test.ts;
import { testDB } from './src/config/db';
import request from 'supertest';
import app from './src/app';

describe('Testing the express route', () => {
 beforeAll(async () => {
   await testDB().catch(err => {
     console.log('err', err.message);
   });
 }, 30000);
});
```

We imported the test database into the test file and then invoked the `beforeAll` method. This method initiates a connection with the test database before proceeding with the execution of other tests.

After the initialization of the database, we will create a test for the `signup` route. In this test, we will check for the route with both valid and invalid values.

This will help to ensure that the application runs as designed and detects errors, if any.

```typescript
// ./app.test.ts
import { testDB } from './src/config/db';
import request from 'supertest';
import app from './src/app';
import { faker } from '@faker-js/faker';

const username = faker.internet.userName(); //Creating a random username
const recipient = faker.internet.userName(); //Creating a random recipient
const password = faker.internet.password(); //Creating a random password

describe('Testing the express route', () => {
 beforeAll(async () => {
   await testDB().catch(err => {
     console.log('err', err.message);
   });
 }, 30000);
 describe('Testing the signup route', () => {
   //Testing the signup route
   test('successful signup test', async () => {
     //Testing the successful signup
     await request(app)
       .post('/api/signup')
       .send({ username, password }) //Sending the username and password in the request body
       .then(res => {
         expect(res.status).toBe(201); //Checking if the status code is 201
       });
     await request(app)
       .post('/api/signup')
       .send({
         username: recipient,
         password
       }) //Sending the username and password in the request body for the second user
       .then(res => {
         expect(res.status).toBe(201); //Checking if the status code is 201
       });
   });
   test('failed signup test', async () => {
     //Testing the failed signup
     await request(app)
       .post('/api/signup')
       .send({}) //Sending an empty request body
       .then(res => {
         expect(res.status).toBe(400); //Checking if the status code is 400
       });
   });
 });
});
```
Based on the above code, we generated fake usernames and a password using the [Faker](https://fakerjs.dev/) library.

Once these fake credentials were created, we created two different tests. In the first API call test, we passed valid data to the request body, while in the second, we passed empty data.

With the creation of the `register` route now in place, we will test the `login` route.

```typescript
// ./app.test.ts
import { testDB } from './src/config/db';
import request from 'supertest';
import app from './src/app';
import { faker } from '@faker-js/faker';
const username = faker.internet.userName(); //Creating a random username
const recipient = faker.internet.userName(); //Creating a random recipient
const password = faker.internet.password(); //Creating a random password
describe('Testing the express route', () => {
 //BeforeAll and Register route test

 describe('Testing the login route', () => {
   //Testing the login route
   test('successful login test', async () => {
     //Testing the successful login
     await request(app)
       .post('/api/login')
       .send({ username, password }) //Sending the username and password in the request body
       .then(res => {
         expect(res.status).toBe(200); //Checking if the status code is 200
       });
   });
   test('failed login test', async () => {
     //Testing the failed login
     await request(app)
       .post('/api/login')
       .send({ username: 'notInDB', password: 'Fred' }) //Sending a username that is not in the database
       .then(res => {
         expect(res.status).toBe(400); //Checking if the status code is 400
       });
   });
 });
});
```

Jest executes these tests in consecutive order; hence, the organization of your tests holds great significance. After testing the `register` route, we used the same username and password to test the login functionality. Next, we will test the `deposit` route.

```typescript
// ./app.test.ts
import { testDB } from './src/config/db';
import request from 'supertest';
import app from './src/app';
import { faker } from '@faker-js/faker';
const username = faker.internet.userName(); //Creating a random username
const recipient = faker.internet.userName(); //Creating a random recipient
const password = faker.internet.password(); //Creating a random password
describe('Testing the express route', () => {
 //BeforeAll, Register, Login route test

 describe('Testing the deposit route', () => {
   //Testing the deposit route
   test('successful deposit test', async () => {
     //Testing the successful deposit
     await request(app)
       .put('/api/deposit')
       .send({ username, password, amount: 5000 }) //Sending the username, password and amount in the request body
       .then(res => {
         expect(res.status).toBe(201); //Checking if the status code is 201
       });
   });
   test('failed deposit test', async () => {
     //Testing the failed deposit
     await request(app)
       .put('/api/deposit')
       .send({ username, password, amount: '0' }) //sending an amount less than 1
       .then(res => {
         expect(res.status).toBe(400); //Checking if the status code is 400
       });
   });
 });
});
```

In the successful test, we deposited a specified amount of money to the same username, while in the invalid test, we passed `0` as the amount to be deposited.

Now that we have money in the user’s account, we will test out transferring this money to another user.

```typescript
// ./app.test.ts
import { testDB } from './src/config/db';
import request from 'supertest';
import app from './src/app';
import { faker } from '@faker-js/faker';
const username = faker.internet.userName(); //Creating a random username
const recipient = faker.internet.userName(); //Creating a random recipient
const password = faker.internet.password(); //Creating a random password
describe('Testing the express route', () => {
 //BeforeAll, Register, Login and Deposit route test

 describe('Testing the transfer route', () => {
   //Testing the transfer route
   test('successful transfer test', async () => {
     //Testing the successful transfer
     await request(app)
       .put('/api/transfer')
       .send({
         username,
         password,
         recipient,
         amount: 1000
       }) //Sending the username, password, recipient and amount in the request body
       .then(res => {
         expect(res.status).toBe(201); //Checking if the status code is 201
       });
   });
   test('failed transfer test', async () => {
     await request(app)
       .put('/api/transfer')
       .send({
         username,
         password,
         recipient,
         amount: '10000'
       }) //Sending an amount greater than the one deposited
       .then(res => {
         expect(res.status).toBe(400); //Checking if the status code is 400
       });
   });
 });
});
```

> `5000` was deposited into the user’s account in the `deposit` route.

In the test marked as successful, we transferred 1000 to the recipient, while in the case of the invalid route, we attempted to transfer a sum exceeding the user's balance.

Lastly, we will test the `find` route.

```typescript
// ./app.test.ts
import { testDB } from './src/config/db';
import request from 'supertest';
import app from './src/app';
import { faker } from '@faker-js/faker';
const username = faker.internet.userName(); //Creating a random username
const recipient = faker.internet.userName(); //Creating a random recipient
const password = faker.internet.password(); //Creating a random password
describe('Testing the express route', () => {
 //BeforeAll, Register, Login and Deposit route test

 describe('Testing the find route', () => {
   //Testing the find route
   test('successful find test', async () => {
     //Testing the successful find
     await request(app)
       .get(`/api/find/${recipient}`) //Sending the username in the request parameters
       .then(res => {
         expect(res.status).toBe(200); //Checking if the status code is 200
       });
   });
   test('failed find test', async () => {
     //Testing the failed find route
     await request(app)
       .get(`/api/find/userNotInDB`) //Sending a username that is not in the database
       .then(res => {
         expect(res.status).toBe(400); //Checking if the status code is 400
       });
   });
 });
});
```
Now that all the tests for the express route have been written, we can now run the test command.
Launch your terminal and execute `npm run test` to initiate the testing process.

Verify that all the written tests have successfully passed. If needed, you can refer to the source code of the tutorial available [here](https://github.com/divofred/apitest).

![Successful Test](https://blog.openreplay.com/images/restful-api-end-to-end-testing/images/s_BD12B51F344068083BF4ECBDCDF9347EB4E48A391AF8A791AD88EEB6D23BF6BC_1691741014696_image.png)

## Conclusion
In this tutorial, we comprehensively covered the process of setting up and conducting tests for an express route. With this basic knowledge, you can conduct tests for complex applications.

## Resources
- [GitHub Repository](https://github.com/divofred/apitest)
- [Jest documentation](https://jestjs.io/docs/getting-started)