Many developers face challenges when it comes to testing their code. Without proper tests, bugs can slip through, leading to frustrated users and costly fixes.
This article will show you how to effectively apply Unit, Integration, and End-to-End testing using Jest, Supertest, and Puppeteer on a very simple example built using Node.js and MongoDB.
By the end of this article, I hope you will have a clear understanding of how to apply these types of tests in your own projects.
?? Please find the full example here in this repo.
Before installing our dependencies, let me introduce our example first. It is a very simple example in which a user can open the registration page, set their registration details, click the registration button, and have their information stored in the database.
In this example, we will use the following packages:
npm install --save jest express mongoose validator npm install --save-dev jest puppeteer jest-puppeteer mongodb-memory-server supertest npm-run-all
Most of these dependencies are straightforward, but here are clarifications for a couple of them:
Nice, let’s translate this into code.
To run your unit tests without unpredictable behaviors, you should reset the mock functions before every test. You can achieve this using the beforeEach hook:
// setup.unit.js beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); });
In this case, we want to test the validateInput function:
npm install --save jest express mongoose validator npm install --save-dev jest puppeteer jest-puppeteer mongodb-memory-server supertest npm-run-all
It is a very simple function that validates if the provided input contains a valid email. Here is its unit test:
// setup.unit.js beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); });
await expect(async () => {}).rejects: Based on the Jest documentation, this is the way to expect the reason for a rejected promise.
Let’s test another function that checks if there is a duplicated email in the database. Actually, this one is interesting because we have to deal with the database, and at the same time, unit testing should not deal with external systems. So what should we do then? Well, we should use Mocks.
First, have a look at the emailShouldNotBeDuplicated function we need to test:
// register.controller.js const validator = require('validator'); const registerController = async (input) => { validateInput(input); ... }; const validateInput = (input) => { const { name, email, password } = input; const isValidName = !!name && validator.isLength(name, { max: 10, min: 1 }); if (!isValidName) throw new Error('Invalid name'); ... };
As you see, this function sends a request to the database to check if there is another user having the same email. Here’s how we can mock the database call:
// __tests__/unit/register.test.js const { registerController } = require('../controllers/register.controller'); describe('RegisterController', () => { describe('validateInput', () => { it('should throw error if email is not an email', async () => { const input = { name: 'test', email: 'test', password: '12345678' }; await expect(async () => await registerController(input)).rejects.toThrow('Invalid email'); }); }); });
We mocked (spied) the database findOne method using jest.spyOn(object, methodName) which creates a mock function and tracks its calls. As a result, we can track the number of calls and the passed parameters of the spied findOne method using the toHaveBeenNthCalledWith.
Before writing our integration test, we have to configure our environment:
npm install --save jest express mongoose validator npm install --save-dev jest puppeteer jest-puppeteer mongodb-memory-server supertest npm-run-all
Now, we are ready to run our integration test.
Let’s test the entire server-side registration process—from sending the registration request to storing user details in the database and redirecting to the success page:
// setup.unit.js beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); });
As you see, the registerController function integrates multiple components (functions), validateInput, emailShouldNotBeDuplicated, and createUser functions.
So, let’s write our integration test:
// register.controller.js const validator = require('validator'); const registerController = async (input) => { validateInput(input); ... }; const validateInput = (input) => { const { name, email, password } = input; const isValidName = !!name && validator.isLength(name, { max: 10, min: 1 }); if (!isValidName) throw new Error('Invalid name'); ... };
Let’s jump into our example.
Actually, in our example, the environment configuration for end-to-end testing is similar to that of integration testing.
In this case, we need to exactly simulate real user registration behavior, from opening the registration page, filling in their details (name, email, password), clicking the “Register” button, and finally being redirected to a success page. Have a look at the code:
npm install --save jest express mongoose validator npm install --save-dev jest puppeteer jest-puppeteer mongodb-memory-server supertest npm-run-all
Let’s break down this code:
Photo by Nathan Dumlao on Unsplash
At this point, you might be wondering how to run all test types simultaneously when each has its own configuration. For example:
So, how can we run all the test types at the same time while ensuring that each respects its corresponding configuration?
To tackle this problem, follow these steps:
1. Let’s create three different configuration files, jest.unit.config.js:
// setup.unit.js beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); });
jest.integration.config.js:
// register.controller.js const validator = require('validator'); const registerController = async (input) => { validateInput(input); ... }; const validateInput = (input) => { const { name, email, password } = input; const isValidName = !!name && validator.isLength(name, { max: 10, min: 1 }); if (!isValidName) throw new Error('Invalid name'); ... };
jest.e2e.config.js:
// __tests__/unit/register.test.js const { registerController } = require('../controllers/register.controller'); describe('RegisterController', () => { describe('validateInput', () => { it('should throw error if email is not an email', async () => { const input = { name: 'test', email: 'test', password: '12345678' }; await expect(async () => await registerController(input)).rejects.toThrow('Invalid email'); }); }); });
2. Next, update your npm scripts in the package.json file as follows:
// register.controller.js const { User } = require('../models/user'); const registerController = async (input) => { ... await emailShouldNotBeDuplicated(input.email); ... }; const emailShouldNotBeDuplicated = async (email) => { const anotherUser = await User.findOne({ email }); if (anotherUser) throw new Error('Duplicated email'); };
--config: Specifies the path to the Jest configuration file.
npm-run-all --parallel: Allows running all tests in parallel.
3. Then, create three setup files named setup.unit.js, setup.integration.js, and setup.e2e.js, containing the necessary setup code used in the previous sections.
4. Finally, run all tests by executing this command npm run test. This command will execute all unit, integration, and end-to-end tests in parallel according to their respective configurations.
In this article, we explored unit, integration, and end-to-end (E2E) testing, emphasizing their importance for building reliable applications. We demonstrated how to implement these testing methods using Jest, Supertest, and Puppeteer in a simple user registration example with Node.js and MongoDB.
In fact, a solid testing strategy not only improves code quality but also boosts developer confidence and enhances user satisfaction.
I hope this article has provided you with useful insights that you can apply to your own projects. Happy testing!
If you found this article useful, check out these articles as well:
Thanks a lot for staying with me up till this point. I hope you enjoy reading this article.
The above is the detailed content of Unit, Integration, and ETesting in One Example Using Jest. For more information, please follow other related articles on the PHP Chinese website!