DEV Community

Cover image for Testing NodeJs/Express API with Jest and Super test ๐Ÿง๐Ÿง
muhammed Lotfy
muhammed Lotfy

Posted on • Edited on

Testing NodeJs/Express API with Jest and Super test ๐Ÿง๐Ÿง

Table Of Contents

Testing one of the most important skills you can known as a developer, It's something a lot of people don't teach or focus on but if you known testing, So I had to try and figure it out here in part(2). you can read more in article Testing part(1) in Node JS with Express using Mocha and Chai

About Testing!

Unit Testing
Tests a single fully isolated unity of the application.

Integration Testing
Tests the interaction of a unit along with its dependencies. e.g. A function that calls another function meaning that the test results are also depending on the function being called within the parent function.

End-2-End Testing
Full flow tests. From the front-end button click to the API endpoint consuming.

Jest and SuperTest Introduction

About Jest

  • Jest is a wonderful testing library created by Facebook to help test JavaScript code, Express APIs, React components, and much more.
  • What's great about Jest is it not only has a similar syntax to other testing/assertion libraries like Jasmine and Chai.
  • And with Jest your tests run in parallel so they are executed much faster than other testing frameworks.

About SuperTest

SuperTest is an HTTP assertions library that allows you to test your Node.js HTTP servers.
It is built on top of SuperAgent library, which is an HTTP client for Node.js.

Who apply testing ?!

(1) Getting Started
So, in this article we're going to use the blog REST API I built using Express and Mongoose
which you can fetch Github repo here

  • To use jest globally we can install it with

$ npm install -g --save-dev jest supertest

  • Once the packages are installed, we need to setup a test command in our package.json file


{
    // ...
    "scripts": {
        "test": "jest"
    }
    // ...
}


Enter fullscreen mode Exit fullscreen mode

(2) Test preparation

  • Writing your first test (You can make the test pass by expecting 1 === 1). ```javascript

// This passes because 1 === 1
it('Testing to see if Jest works', () => {
expect(1).toBe(1)
})

**(3) Testing Endpoints**
 - Before you can test endpoints, you need to setup the server so Supertest can use it in your tests.
 - Most tutorials teach you to listen to the Express app in the server file, like this:
```javascript


const express = require('express')
const app = express()

// Middlewares...
// Routes...

app.listen(3000)


Enter fullscreen mode Exit fullscreen mode
  • This doesnโ€™t work because it starts listening to one port. If you try to write many test files, you will get an error port in use.
  • So you want to allow each test file to start a server on their own. To do this, you need to export app without listening to it.


const express = require('express')
const app = express()

// Middlewares...
// Routes...

module.exports = app


Enter fullscreen mode Exit fullscreen mode
  • For development or production purposes, you can listen to your app like normal in a different file like index.js. ```javascript

const app = require("./server");
app.listen(5000, () => {
console.log("Server has started!");
});

**(4) Using Supertest**
 - To use Supertest, you require your app and supertest in the test file.
```javascript


const app = require("../server");
const mongoose = require("mongoose");
const supertest = require("supertest");

beforeEach((done) => {
  mongoose.connect("mongodb://localhost:27017/JestDB",
    { useNewUrlParser: true, useUnifiedTopology: true },
    () => done());
});

afterEach((done) => {
  mongoose.connection.db.dropDatabase(() => {
    mongoose.connection.close(() => done())
  });
});


Enter fullscreen mode Exit fullscreen mode
  • Those are functions that will be invoked before and after every single test cases. This allows us to connect to MongoDB and remove all the data once a test case is finished
  • In Jest, these are done using four different functions:

    • beforeAll - called once before all tests.
    • beforeEach - called before each of these tests (before every test function).
    • afterEach - called after each of these tests (after every test function).
    • afterAll - called once after all tests.

(5) Using routes

  • We also want to initialize our Express server in app variable which will be accessible from our test case. Let's create a new test case called GET /api/posts. ```javascript

test("GET /api/posts", async () => {
const post = await Post.create({ title: "Post 1", content: "Lorem ipsum" });

await supertest(app).get("/api/posts")
.expect(200)
.then((response) => {
// Check type and length
expect(Array.isArray(response.body)).toBeTruthy();
expect(response.body.length).toEqual(1);

  // Check data
  expect(response.body[0]._id).toBe(post.id);
  expect(response.body[0].title).toBe(post.title);
  expect(response.body[0].content).toBe(post.content);
});
Enter fullscreen mode Exit fullscreen mode

});

* Here, we're adding a new document to our database so that we won't get an empty response. Then, we send a GET request using SuperTest to the /api/posts endpoint and expect the response status to be 200

  • which means success. Finally, we check if the response matches the data in the database.

We can now run our tests by running npm test

Alt Text

Matchers

Jest has quite a few functions used for assertions/expectations. You can see a full list here, but these are some common ones.

  • toBeDefined
  • toBeGreaterThan / toBeLessThan
  • toBe (uses === to compare)
  • toEqual (for deep object comparison)
  • toContain (see if a value is inside of a collection)

Now, let's test the get single post .



test("GET /api/posts/:id", async () => {
  const post = await Post.create({ title: "Post 1", content: "Lorem ipsum" });

  await supertest(app).get("/api/posts/" + post.id)
    .expect(200)
    .then((response) => {
      expect(response.body._id).toBe(post.id);
      expect(response.body.title).toBe(post.title);
      expect(response.body.content).toBe(post.content);
    });
});


</code></pre></div><h3>
  <a name="we-can-now-run-our-tests-again-by-running-raw-npm-test-endraw-" href="#we-can-now-run-our-tests-again-by-running-raw-npm-test-endraw-">
  </a>
  We can now run our tests again by running <code>npm test</code>
</h3>

<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/1mvul58a5uawa4esdljw.PNG" alt="Alt Text"></p>
<h1>
  <a name="conclusion" href="#conclusion">
  </a>
  Conclusion! <a name="chapter-4"></a>
</h1>

<p>I played around with testing lately. One thing I tried to do was to test the endpoints of my Express application.</p>

<p>If you enjoyed this article, please tell a friend about it!<br>
Share it on Twitter. If you spot a typo, Iโ€™d appreciate if you can correct it on <a href="https://github.com/mohamedlotfe/unit-testing-api-nodejs-jest">GitHub</a>.<br>
You can contact me through:</p>

<p>Gmail: <a href="mailto:mhmdlotfy9@gmail.com">mhmdlotfy9@gmail.com</a><br>
or <a href="https://www.linkedin.com/in/mohamedlotfybasher/">Linkedin</a><br>
Thank you</p>
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Top comments (8)

Collapse
 
aurelkurtula profile image
aurel kurtula

I've started playing with this and do tests run in the order I write them and can/should I rely on the actions of the previous one. For example I need to test whether I can add a post, and whether I can recieve a post, rather than doing what you did (at "Now, let's test the get single post"). Is it bad practice to write the something like this

const data = { id: "" };
describe("adding to the db", () => {
  test("add post", (done) => {
   supertest(app).post("/api/post")
   .end((response) => {
      data.id = payload.id;
      expect(response.body.title).toBe(post.title);
      expect(response.body.content).toBe(post.content);
    done();
    }); 

  });
  test("get one post", (done) => {
    request(app)
      .get(`/api/${data.id}`)
      .end((err, res) => {
        expect("something useful").toEqual("something useful");
        done();
      });
  });
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mhmdlotfy96 profile image
muhammed Lotfy

Hey Aural
firstly, this is a a base article a bout how to use unit test.
and second one that what you did is just the post (or data) variable shared for creating and fetching steps not a big deal so far.
and last thing is to Mock your data "preparing step" then use it whatever you need
check this lib npmjs.com/package/given2

Collapse
 
aurelkurtula profile image
aurel kurtula

Thanks

Collapse
 
ibrahimkamal profile image
Ibrahim Kamal

another great article
teslam edak ya sadeky

Collapse
 
mhmdlotfy96 profile image
muhammed Lotfy

thanks

Collapse
 
ayushlab profile image
Ayush verma

very helpful

Collapse
 
mhmdlotfy96 profile image
muhammed Lotfy

thank you

Collapse
 
amitkum66494760 profile image
Amit Yadav

great article, thanks.