# No one should need Postman/Insomnia to test an endpoint

## Intro

Hey, how's it going? I'm Erick Wendel and today I'm going to show you an incredible technique to improve your **productivity** and, at the same time, improve the **quality of your application delivery**.

I saw this [tweet](https://twitter.com/masoxi22/status/1638576680850661398?s=20) (in Brazilian Portuguese) last week where the author said that **no one should need Postman/Insomnia to test an endpoint**. I agree with his opinion 100% and in this post, I'll show you **why** with **practical examples** and a **project** that you can replicate in your daily work.

Are you ready to see it?

## Why not?

**Postman** and **Insomnia** are extremely useful tools to help us consume Web APIs. In our daily work, we usually put the URL of the project we're developing to validate what the application returns.

The problem is that this process becomes **repetitive**. You make a change in your code, restart the server, go to Postman, change the parameters that will be sent to the Web API, send the request, and check the result.

And then, you notice that the API returned a wrong result, very different from what you wanted. So, you modify the program and repeat the whole process.

This vicious cycle makes you **waste** A LOT of time, as there are several steps between restarting the server, changing the tool, and sending the request.

There is a much better way that also improves your software quality. Instead of manually testing your application with these tools, you can create automated tests.

With automated tests, the process is much less painful. You define what you expect that endpoint to return, and for every change you make in the code, the test suite is restarted, and you see the result instantly on the screen 🤯

Not only do you **save time**, but you also enable an important possibility. Since all your endpoints are properly tested, you can include a **Continuous Integration** pipeline.

Continuous Integration is an extremely important and widely used process out there. Imagine the following: your application has been working for months, and some tired developer went there and uploaded broken code.

The continuous integration pipeline will run the tests you had already created and confirm that something went wrong, blocking that update.

Thus, your users will never suffer from **critical bugs** or **red screens** in their applications, you get a good night's sleep, and the software grows healthily.

So, that's what I'm going to show you now. To make it a bit more challenging, I'll take an already functional application with some endpoints and implement new routes.

I'll show you how this process is done in Postman and how painful this trial and error can be. I'll also show you how tests will improve your quality of life as a developer.

**IMPORTANT**: The testing technique I'm going to show you today is called end-to-end testing. That means you're going to test the application from the point of view of a user who doesn't know the application's internal implementation.

This is extremely useful for validating the application as a whole, but over time, test automation can take a long time to run, and it's important to balance it with unit tests.

I have a video on this channel talking about unit tests as well but don't worry, for today's class, you don't need any prior knowledge.

I want to **open your mind** to a world of possibilities where you code faster and still build quality software.

Enough talking, let's get started!

## Using Postman - Understanding the problem

The problem is not using Postman itself, as I said before the idea here is to get a routine where you can write code, get instant feedback, and still deliver a maintainable codebase.

### Workflow while using Postman and similars

When we use those kinds of tools we generally follow these steps:

1. Write the server and some Web API route
    
2. Go to the postman and make an HTTP request
    
3. Change the code in the backend
    
4. Reload the server
    
5. Go back to Postman and make another request
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681422883650/7ac0a6ba-3e52-47e5-9a23-e2a41a5f097c.png align="center")

To save time, you'd create some testing scripts on Postman to save the token and put it on every request through environment variables such as:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681422915536/ccc0eabf-851f-4486-9fdc-8aa81ed8e295.png align="center")

And when you need to request a private route you'd refer to the variable on the Request Headers such as:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681422980977/d8bf7a92-daca-43a4-90ff-cde902ece893.png align="center")

Still, as the project grows, it would be messy, and a bunch of URLs and variables being defined there.

Going back and forth to the application interface to check logs, debug code results, and validate a workflow makes us waste a lot of time.

Postman is not a tool to make E2E tests and as far as I know, we can't run a Continuous Integration process using it.

**What if we could see the results instantly directly from our software development environment and still save time while doing it?**

## Getting Started

I've created a small code template with a few tests to simulate how to implement tests on an existing application.

**IMPORTANT:** **This post doesn't focus on best practices, the goal is to show you how you can improve productivity and write tests on your existing applications.**

[Go there and clone this repo](https://github.com/ErickWendel/postman-is-slowing-you-down/tree/template). This project uses **Node.js v19** as you'll be using the latest Node.js features for testing and watching for hot reload.

This repo is part of [my other blog post on Node.js e2e testing,](https://hashnode.com/post/clgdnfjj50243nqnv42bgca3j) you'd take a look later to get some context.

### Making sure the template is ok

After restoring the dependencies with `npm ci` and making sure you're using Node.js in the **v19.8** using `node -v` run `npm run test` to make sure everything is working as expected.

You should see the output below:

```javascript
npm run dev

> app@0.0.1 dev
> node --watch api.js

(node:20514) ExperimentalWarning: Watch mode is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
listening to 3000
```

Let's take a look at what is there.

```javascript
// api.js
async function loginRoute(request, response) {
  const { user, password } = JSON.parse(await once(request, "data"))
  if (user !== VALID.user || password !== VALID.password) {
    response.writeHead(400)
    response.end(JSON.stringify({ error: 'user invalid!' }))
    return
  }

  const token = jsonwebtoken.sign({ user, message: 'heyduude' }, TOKEN_KEY)

  response.end(JSON.stringify({ token }))
}
```

This template will authenticate users and return a JWT Token so consumers and use it to make requests to private routes.

Looking at the code on the template you'll see that there're two routes defined there the **/login** and whatever other route you try accessing will either return **not found** if requested with a valid token or an **invalid token** error if the opposite.

```javascript
// api.js
async function handler(request, response) {
  if (request.url === '/login' && request.method === "POST") {
    return loginRoute(request, response)
  }

  if (!validateHeaders(request.headers)) {
    response.writeHead(400)
    return response.end("invalid token!")
  }

  response.writeHead(404)
  response.end('not found!')
}
```

You're gonna write tests for all existing code and then you're gonna implement a new route to create products.

### Understanding the business requirements

To make things more interesting I'll define below the requirements for the route you're gonna implement:

1. The **POST /product** will receive the product's **description** and **price** from the request and return what category this product belongs to.
    
2. If the product **price** is higher than 100 the endpoint will respond with the **premium** category
    
3. if the product **price** is between 50 and 99, the endpoint will respond with the **regular** category
    
4. if the product **price** is less than 50, the endpoint will respond with the **basic** category
    

The product route is a private route which means if a consumer wants to access it, they must send a valid **JWT Token** on the Request headers.

### Setting up the testing environment

Create an empty file called **api.test.js** in the same folder as your **package.json** file is located.

Head to the package.json and add the scripts as follows:

```json
  "scripts": {
    "dev": "node --watch api.js",
    "test:dev": "node --watch --test api.test.js",
    "test": "node --test api.test.js"
  },
```

If you're not familiar with the Node.js native test runner, I recommend you take a look at the recent post I wrote on [How to create E2E tests in Node.js](https://blog.erickwendel.com.br/how-to-create-e2e-tests-in-nodejs-with-no-frameworks-step-by-step) here in the blog. But don't worry it's not a requirement for following this tutorial.

Go to your terminal and run `npm run test:dev` the output should be as follows:

```javascript
npm run test:dev

> app@0.0.1 test:dev
> node --watch --test api.test.js

✔ /Users/erickwendel/Downloads/projetos/postman-is-slowing-you-down/template/api.test.js (38.097208ms)
```

You're all set to start creating tests.

You're gonna need to import the **app.js** file in this test so you'd spin up the server and start making requests.

**Notice** that you'll be using the **watch mode** which means you must make sure that before the code is restarted you'd stop the server and prevent errors of two program instances trying to run in the same port.

In the **api.test.js** file, paste the code below:

```javascript
import { describe, before, after, it } from 'node:test'
import { deepEqual, deepStrictEqual, ok } from 'node:assert'
const BASE_URL = `http://localhost:3000`
describe('API Products Test Suite', () => {
  let _server = {}
  let _globalToken = ''

  before(async () => {
    _server = (await import('./api.js')).app

    await new Promise(resolve => _server.once('listening', resolve))
  })
  after(done => _server.close(done))
})
```

The code above will import the API.js file and wait until the server is ready to receive requests using the **before** function.

Using the **after** function, you make sure that after the entire testing suite has been executed it'll close the server.

Your terminal now should look like this output:

```javascript
npm run test:dev

> app@0.0.1 test:dev
> node --watch --test api.test.js

✔ /Users/erickwendel/Downloads/projetos/postman-is-slowing-you-down/template/api.test.js (38.097208ms)
ℹ listening to 3000
✔ API Products Test Suite (54.460625ms)
```

This means that the server is being started and everything is working as expected.

### Writing E2E Tests

As it's required for all routes but the login route to pass through a JWT Token we'll add a new **before** step which will request the API, get the token and store it on the **\_globalToken** variable.

Inside the **describe block** paste the code below:

```javascript
 async function makeRequest(url, data) {
    const request = await fetch(url, {
      method: 'POST',
      body: JSON.stringify(data),
      headers: {
        authorization: _globalToken
      }
    })
    deepEqual(request.status, 200)
    return request.json()
  }
  async function setToken() {
    const input = {
      user: 'erickwendel',
      password: '123'
    }

    const data = await makeRequest(`${BASE_URL}/login`, input)
    ok(data.token, 'token should be present')
    _globalToken = data.token
  }
```

Notice how straightforward is this code. You're using the **fetch** which is a global function used to make requests.

In the past, we usually had to install third libraries such as [**supertest**](https://www.npmjs.com/package/supertest) but it's not needed to install any library as you'd do using only the fetch module.

And right after the existing **before** function call, you'd add a new **before** function calling the **setToken** function you just pasted.

The end of the file will look like the below:

```javascript
  before(async () => {
    _server = (await import('./api.js')).app

    await new Promise(resolve => _server.once('listening', resolve))
  })
  before(async () => setToken())
  after(done => _server.close(done))
```

This will make sure the **\_globalToken** variable will always be populated before hitting the endpoints.

You haven't implemented the **/product** route yet. Before implementing it let's implement the test first.

```javascript
  it('it should create a premium product', async () => {
    const input = {
      description: "tooth brush",
      price: 101
    }
    const data = await makeRequest(`${BASE_URL}/products`, input)
    deepStrictEqual(data.category, "premium")
  })
```

Your terminal should now show an error such as:

```javascript
▶ API Products Test Suite
  ✖ it should create a premium product (6.095959ms)
    AssertionError: Expected values to be loosely deep-equal:
    
    404
    
    should loosely deep-equal
    
    200
        at makeRequest (file:///Users/erickwendel/Downloads/projetos/postman-is-slowing-you-down/template/api.test.js:15:5)
        at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
        at async TestContext.<anonymous> (file:///Users/erickwendel/Downloads/projetos/postman-is-slowing-you-down/template/api.test.js:39:18)
        at async Test.run (node:internal/test_runner/test:548:9)
        at async Promise.all (index 0)
        at async Suite.run (node:internal/test_runner/test:801:7)
        at async startSubtest (node:internal/test_runner/harness:192:3) {
      generatedMessage: false,
      code: 'ERR_ASSERTION',
      actual: 404,
      expected: 200,
      operator: 'deepEqual'
    }

▶ API Products Test Suite (86.56325ms)
```

Now you would implement it! Let's head back to the **api.js file** and implement this route.

First let's create the function with all the business requirements listed in the previous section:

```javascript
// api.js
async function createProductRoute(request, response) {
  const { description, price } = JSON.parse(await once(request, "data"))
    const categories = {
      premium: {
        from: 101,
        to: 500
      },
      regular: {
        from: 51,
        to: 100
      },
      basic: {
        from: 0,
        to: 50
      },
    }
    const category = Object.keys(categories).find(key => {
      const category = categories[key]
      return price >= category.from && price <= category.to
    })
    response.end(JSON.stringify({ category }))
}
```

Then let's jump to the **handler** function and implement the logic for receiving POST requests related to this endpoint.

The full implementation for the **handler** function will look like below:

```javascript
// api.js
async function handler(request, response) {
  if (request.url === '/login' && request.method === "POST") {
    return loginRoute(request, response)
  }

  if (!validateHeaders(request.headers)) {
    response.writeHead(404)
    return response.end("invalid token!")
  }

  if (request.url == '/products' && request.method === "POST") {
    return createProductRoute(request, response)
  }

  response.writeHead(404)
  response.end('not found!')
}
```

Now, checking the output in the terminal the test should pass

```javascript
npm run test:dev

> app@0.0.1 test:dev
> node --watch --test api.test.js

ℹ listening to 3000
▶ API Products Test Suite
  ✔ it should create a premium product (4.854667ms)
▶ API Products Test Suite (79.295167ms)
```

Niiiiiiiiiiiiice! Now it's the easiest part. Let's implement all the tests for the remaining business logic. Jump to the **api.test.js** and paste the test cases below after the last **it.**

```javascript
  it('it should create a regular product', async () => {
    const input = {
      description: "escova de dente",
      price: 70
    }
    const data = await makeRequest(`${BASE_URL}/products`, input)
    deepStrictEqual(data.category, "regular")
  
  })

  it('it should create a basic product', async () => {
    const input = {
      description: "fio",
      price: 2
    }
    const data = await makeRequest(`${BASE_URL}/products`, input)
    deepStrictEqual(data.category, "basic")
  
  })
```

All tests should pass

```javascript
▶ API Products Test Suite
  ✔ it should create a premium product (12.418709ms)
  ✔ it should create a regular product (3.384458ms)
  ✔ it should create a basic product (2.473792ms)
▶ API Products Test Suite (100.180334ms)
```

Nice isn't it?

In case you're struggling in some part here is the full code for the **api.js** file

```javascript
import jsonwebtoken from 'jsonwebtoken'
import { once } from 'node:events'
import { createServer } from 'node:http'

const VALID = {
  user: 'erickwendel',
  password: '123'
}
const TOKEN_KEY = "abc123"

async function loginRoute(request, response) {
  const { user, password } = JSON.parse(await once(request, "data"))
  if (user !== VALID.user || password !== VALID.password) {
    response.writeHead(400)
    response.end(JSON.stringify({ error: 'user invalid!' }))
    return
  }

  const token = jsonwebtoken.sign({ user, message: 'heyduude' }, TOKEN_KEY)

  response.end(JSON.stringify({ token }))
}

function validateHeaders(headers) {
  try {
    const auth = headers.authorization.replace(/bearer\s/ig, '')
    jsonwebtoken.verify(auth, TOKEN_KEY)
    return true
  } catch (error) {
    return false
  }
}
async function createProductRoute(request, response) {
  const { description, price } = JSON.parse(await once(request, "data"))
    const categories = {
      premium: {
        from: 101,
        to: 500
      },
      regular: {
        from: 51,
        to: 100
      },
      basic: {
        from: 0,
        to: 50
      },
    }
    const category = Object.keys(categories).find(key => {
      const category = categories[key]
      return price >= category.from && price <= category.to
    })
    response.end(JSON.stringify({ category }))
}
async function handler(request, response) {
  if (request.url === '/login' && request.method === "POST") {
    return loginRoute(request, response)
  }

  if (!validateHeaders(request.headers)) {
    response.writeHead(400)
    return response.end("invalid token!")
  }

  if (request.url == '/products' && request.method === "POST") {
    return createProductRoute(request, response)
  }

  response.writeHead(404)
  response.end('not found!')
}

const app = createServer(handler)
  .listen(3000, () => console.log('listening to 3000'))

export { app }
```

And also for the **api.test.js** file

```javascript
import { describe, before, after, it } from 'node:test'
import { deepEqual, deepStrictEqual, ok } from 'node:assert'
const BASE_URL = `http://localhost:3000`
describe('API Products Test Suite', () => {
  let _server = {}
  let _globalToken = ''
  async function makeRequest(url, data) {
    
    const request = await fetch(url, {
      method: 'POST',
      body: JSON.stringify(data),
      headers: {
        authorization: _globalToken
      }
    })
    deepEqual(request.status, 200)
    return request.json()
  }
  async function setToken() {
    const input = {
      user: 'erickwendel',
      password: '123'
    }

    const data = await makeRequest(`${BASE_URL}/login`, input)
    ok(data.token, 'token should be present')
    _globalToken = data.token
  }
  before(async () => {
    _server = (await import('./api.js')).app

    await new Promise(resolve => _server.once('listening', resolve))
  })
  before(async () => setToken())
  it('it should create a premium product', async () => {
    const input = {
      description: "tooth brush",
      price: 101
    }
    const data = await makeRequest(`${BASE_URL}/products`, input)
    deepStrictEqual(data.category, "premium")
  
  })
  it('it should create a regular product', async () => {
    const input = {
      description: "escova de dente",
      price: 70
    }
    const data = await makeRequest(`${BASE_URL}/products`, input)
    deepStrictEqual(data.category, "regular")
  
  })

  it('it should create a basic product', async () => {
    const input = {
      description: "fio",
      price: 2
    }
    const data = await makeRequest(`${BASE_URL}/products`, input)
    deepStrictEqual(data.category, "basic")
  
  })
  after(done => _server.close(done))
})
```

## Wrapping Up

You start seeing it paying off as any change you make to your code will instantly reflect on your terminal. You won't need to go back and forth to the **Postman** or similar.

Once you set up the testing environment you copy and paste the testing cases and change the input and expected result.

You save a lot of time and any new developer who wants to get to know what endpoints and how to access them would access the testing environment and test the code as a whole.

As you've written tests, you'd add a Continuous Integration step, such as the **GitHub Actions.**

This is important for testing all endpoints and checking whether the new code or new releases will break any part of your application and prevent it from crashing on the face of your users.

### Homework

Your homework is to cover the remaining endpoints and add more features to the application and refactor this ugly code.

Now that you have understood how the step-by-step process works, I encourage you to try testing existing applications in your work and cover the main paths that clients access.

This is a great way to learn and improve your skills in JavaScript.

### Thanks for reading

I am always available to answer your questions in the comments section. Also, let me know in the comments what kind of content you'd like to see next.

I hope you enjoyed this post and found it helpful please give it a thumbs up in this post as it helps me a lot.

I hope this content has exceeded your expectations. Thank you very much for reading, and I'm Erick Wendel. See you in the next post!
