In front-end testing, the testing framework plays a crucial role in organizing and executing test cases. It allows the test code to be structured and stored in specific test files. The framework also automates the running of tests and provides a clear display of the test results. Now, let's explore the commonly used testing frameworks and delve into their advantages and disadvantages.
Front-end testing can generally be divided into the following three types:
- Unit testing : Separate various parts of the code to check and verify the smallest testable unit in the software;
- Integration testing : Test whether multiple units can work together;
- End-to-End Testing (E2E) : Testing the entire software product from start to finish to ensure that the application flow works as expected.
Jest
Jest, created by Facebook, is a popular testing framework for JavaScript. It is highly recommended for testing React applications and has strong support from the React community.
Github: https://github.com/facebook/jest
Jest has the following features:
- Compatibility : In addition to testing React applications, it can also be easily integrated into other applications and is compatible with Angular, Node, Vue and other Babel-based projects.
- Automatic mocking : When importing libraries in test files, Jest automatically mocks these libraries to help us use them easily.
- Extension API : Jest provides an extensive API, and there is no need to include additional libraries unless you really need them.
- Timer Simulation : Jest has a time simulation system that is great for fast-forwarding timeouts in your application and helps save time when running tests.
- Active community : Jest has a very active community that helps us find solutions quickly when needed.
Sample code:
const sum = require('./sum');
test('1 + 2 = 3', () => {
expect(sum(1, 2)).toBe(3);
});
}
Mocha
Mocha is a testing framework for JavaScript, having a rich feature-set that can be used in both node.js and the browser. With Mocha, testing asynchronous functions is effortless and enjoyable. It constantly runs tests, allowing useful and flexible reporting, and linking any unhandled exceptions to the appropriate test cases. However, Mocha does not naturally support assertions, mocking, etc. and addons must be included to support these features. The most popular assertion libraries that can be used along with Mocha include Chai, Assert, Should.js, and Better-assert.
Github: https://github.com/mochajs/mocha
Mocha has the following features:
- Simple to use : Mocha is a simple solution for smaller projects that don't contain complex assertions or testing logic.
- ES module support : Mocha supports writing tests as ES modules, not just using CommonJS.
Of course, Mocha also has shortcomings:
- Difficult to set up : Having to use an additional assertion library does mean it's harder to set up than other libraries.
- Potential inconsistency with plugins : Mocha includes test structures as globals, saving time by not having to use includeors in every file. The disadvantage of require is that plugins may require these to be included anyway, causing inconsistencies.
- No support for arbitrary transpilers : Before v6.0.0, Mocha had a feature that allowed the use of arbitrary transpilers, such as coffee-script, etc., but it has now been deprecated.
Sample code:
var assert = require('assert');
describe('Array', function () {
describe('#indexOf()', function () {
it('should return -1 when the value is not present', function () {
assert.equal([1, 2, 3].indexOf(4), -1);
});
});
});
Cypress
Cypress is a cutting-edge front-end testing tool designed specifically for the modern web. With Cypress, developers can create various types of tests, including end-to-end, integration, and unit tests. What sets Cypress apart is that it runs directly in real browsers like Chrome, Firefox, and Edge, without the need for driver binaries. This allows developers to have full control over the application being tested, as both the automation code and the application code reside on the same platform. Cypress is renowned for its exceptional end-to-end testing capabilities, which enable developers to simulate user actions and identify any deviations whenever new code is deployed.
Cypress has the following features:
- End-to-end testing : Since Cypress runs in a real browser, it can be relied upon for end-to-end user testing.
- Timeline Snapshot Testing : As you execute, Cypress takes a snapshot of that moment and allows developers or QA testers to see what happened at a specific step.
- Stable and reliable : Compared with other testing frameworks, Cypress provides stable and reliable test execution results.
- Documentation and Community : From scratch to running, Cypress includes all the necessary information to help developers get up to speed, and it also has an active community.
- Fast : Cypress's test execution speed is very fast, with response time of less than 20 milliseconds.
However, it is important to note that Cypress can only run tests in a single browser.
Sample code:
Cypress.Commands.add('login', (username, password) => {
cy.visit('/login')
cy.get('input[name=username]').type(username)
// {enter} causes the form to submit
cy.get('input[name=password]').type(`${password}{enter}`, { log: false })
// we should be redirected to /dashboard
cy.url().should('include', '/dashboard')
// our auth cookie should be present
cy.getCookie('your-session-cookie').should('exist')
// UI should reflect this user being logged in
cy.get('h1').should('contain', username)
})
it('does something on a secured page', function () {
const { username, password } = this.currentUser
cy.login(username, password)
// ...
})
Github: https://github.com/cypress-io/cypress
Storybook
Storybook is a unique UI testing tool that is designed to test components within an isolated environment. Unlike other JavaScript testing frameworks, Storybook also offers a variety of tools, a test runner, and easy integration with the broader JavaScript ecosystem. This allows front-end developers to expand their UI testing coverage effortlessly.
There are several ways to use Storybook for UI testing:
- Visual testing : Capture screenshots of each story and compare them to the baseline to detect appearance and integration issues.
- Accessibility Testing : Identifies usability issues related to visual, hearing, mobility, cognitive, language, or neurological impairments.
- Interaction testing : Verify component functionality by simulating user behavior, triggering events, and ensuring state updates as expected.
- Snapshot Test : Detect changes in render markup to display surface rendering errors or warnings.
- Import stories from other tests into QA and even more UI features.
Jasmine
Jasmine is a unit testing framework for JavaScript which is uncomplicated and free from any dependencies on browsers, DOM, or other JavaScript. It can be used to test any website, Node.js project, or JavaScript-based program. Jasmine is widely known for its Behavior-Driven Development (BDD) tool which involves creating tests before writing the actual code, different from Test-Driven Development (TDD).
Jasmine has the following features:
- API simplicity : It provides a concise and easy-to-understand syntax, and a rich and straightforward API for writing unit tests.
- Works out of the box : No additional assertion or mocking libraries required, it works out of the box.
- Fast : It is relatively fast as it does not rely on any external libraries.
- Multi-language : Not only for writing JS tests, but also for Ruby (via Jasmine-gem) or Python (via Jsmin-py)
Of course, Jasmine also has shortcomings:
- Pollute the global environment : By default it creates test global variables (keywords like "describe" or "test") so you don't have to import them in your tests. This can be a disadvantage in certain circumstances.
- Writing asynchronous tests is challenging : Testing asynchronous functions using Jasmine is difficult.
Sample code:
describe("helloWorld", () => {
it("returns hello world", () => {
var actual = helloWorld();
expect(actual).toBe("hello world");
});
}
)
React Testing Library
React Testing Library is a lightweight solution designed specifically for testing React components. Built on top of DOM Testing Library, it provides APIs that allow easy operations and access to the DOM. Unlike other testing libraries, it does not focus on the internal implementation of the component but rather on testing. React Testing Library encourages better testing practices and is based on both react-dom and react-dom/test-utils. It can be used in conjunction with test runners like Jest and is not a test runner itself. The utilities it provides enable the rendering of components into the virtual DOM and facilitate locating and interacting with them. In summary, React Testing Library is an efficient tool for writing maintainable and practical React component tests that make the tests more confidence-inspiring.
Github: https://github.com/testing-library/react-testing-library
React Testing Library has the following features:
- React official recommendation : References and suggestions for using this library can be found in React's official documentation.
- Small size : It is specially written for testing React apps/components.
Sample code:
import React, {useEffect} from 'react'
import ReactDOM from 'react-dom'
import {render, fireEvent} from '@testing-library/react'
const modalRoot = document.createElement('div')
modalRoot.setAttribute('id', 'modal-root')
document.body.appendChild(modalRoot)
const Modal = ({onClose, children}) => {
const el = document.createElement('div')
useEffect(() => {
modalRoot.appendChild(el)
return () => modalRoot.removeChild(el)
})
return ReactDOM.createPortal(
<div onClick={onClose}>
<div onClick={e => e.stopPropagation()}>
{children}
<hr />
<button onClick={onClose}>Close</button>
</div>
</div>,
el,
)
}
test('modal shows the children and a close button', () => {
// Arrange
const handleClose = jest.fn()
// Act
const {getByText} = render(
<Modal onClose={handleClose}>
<div>test</div>
</Modal>,
)
// Assert
expect(getByText('test')).toBeTruthy()
// Act
fireEvent.click(getByText(/close/i))
// Assert
expect(handleClose).toHaveBeenCalledTimes(1)
})
Playwright
Playwright is a tool created by Microsoft that helps with end-to-end testing automation. The unique advantage of Playwright is that it runs smoothly on all major browser engines like Chromium, Webkit, and Firefox. It was derived from the famous Puppeteer project, but unlike Puppeteer, developers and testers specifically design Playwright for end-to-end testing. Playwright also meshes well with major CI/CD servers, including TravisCI, CircleCI, Jenkins, Appveyor, Github actions, and much more.
Playwright has the following features:
- Multi-language : Playwright supports multiple languages such as JavaScript, Java, Python and .NET C#;
- Multiple Test Runner support : can be used by Mocha, Jest and Jasmine;
- Cross-browser : The main goal of this framework is to support all major browsers.
- Impersonation and native event support : Mobile devices, geolocations, and permissions can be emulated, and native input events using mouse and keyboard are supported.
Of course, Playwright also has some disadvantages:
- Still in the early stages : fairly new, limited community support;
- No support for real devices : Real devices for mobile browser testing are not supported, but emulators are.
Sample code:
import { test, expect } from '@playwright/test';
test('my test', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Playwright/);
// Expect an attribute "to be strictly equal" to the value.
await expect(page.locator('text=Get Started').first()).toHaveAttribute('href', '/docs/intro');
await page.click('text=Get Started');
// Expect some text to be visible on the page.
await expect(page.locator('text=Introduction').first()).toBeVisible();
});
Vitest
Vitest is a unit testing framework that runs very fast with the help of Vite. It has support for TypeScript/JSX out of the box, and smart watch modes like HMR (hot module replacement) to enhance the testing experience. Vitest pays close attention to performance by utilizing worker threads for parallel testing. It comes with built-in Tinyspy for simulating, marking, and monitoring waiting times. Also, its configuration, converters, parsers, and plugins are consistent with Vite.
Vitest has the following features:
- Vite support : Reuse Vite configurations, converters, parsers, and plugins to stay consistent across applications and tests.
- Compatible with Jest : With expectations, snapshots, overrides, and more — migrating from Jest is easy.
- Intelligent instant browsing mode : Intelligent file monitoring mode, just like the tested HMR.
- ESM, TypeScript, JSX : Out-of-the-box ESM, TypeScript and JSX support provided by esbuild.
- In-source testing : Provides a way to run tests and implementations in source code, similar to Rust's module testing.
However, Vitest is still in its early stages (the latest version is 0.28.1). Although the team behind Vitest has done a lot of work in creating this tool, it is still young and community support may not be complete yet.
Sample code:
test('Math.sqrt()', () => {
expect(Math.sqrt(4)).toBe(2)
expect(Math.sqrt(144)).toBe(12)
expect(Math.sqrt(2)).toBe(Math.SQRT2)
})
test('JSON', () => {
const input = {
foo: 'hello',
bar: 'world',
}
const output = JSON.stringify(input)
expect(output).eq('{"foo":"hello","bar":"world"}')
assert.deepEqual(JSON.parse(output), input, 'matches original')
})
AVA
Ava is a test runner that uses the asynchronous nature of JavaScript to run tests simultaneously, ensuring better performance. It doesn't create any globals, making it easier to control which features are used in the testing process. This can simplify testing by providing clarity and ensuring transparency in the execution process.
Github: https://github.com/avajs/ava
AVA has the following characteristics:
- Run tests at the same time : Take advantage of the asynchronous nature of JavaScript to make testing simple and minimize waiting time between deployments;
- Simple API : Through a simple API, only the required content is provided;
- Snapshot testing : provided by jest-snapshot, this is very useful when you want to know when the UI of your application changes unexpectedly;
- Tap format reports : Ava displays human-readable reports by default, and TAP format reports are also available.
Of course, AVA also has some disadvantages:
- No test grouping : Ava cannot group similar tests together.
- No built-in mocking : Ava does not come with mocking, but third-party libraries such as Sinon.js can be used.
Sample code:
import test from 'ava';
test('foo', t => {
t.pass();
});
test('bar', async t => {
const bar = Promise.resolve('bar');
t.is(await bar, 'bar');
});