As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!
When I first started writing unit tests in JavaScript, I found the process confusing and often skipped it to save time. Over the years, I've learned that good unit testing is like having a safety net—it catches mistakes before they cause bigger problems. Jest has become my go-to tool for this because it's straightforward and powerful. Let me share some techniques that have made testing not just manageable, but an essential part of my workflow.
Unit testing checks small pieces of code to make sure they work as expected. Imagine you have a function that adds two numbers. You want to test it with different inputs to be confident it always gives the right answer. Jest helps by providing a clean way to write these checks. I'll walk through eight key methods that have improved my testing habits, with plenty of code to show how they work in practice.
Starting with test structure, I like to think of it as organizing a toolbox. Grouping related tests makes them easier to read and maintain. In Jest, I use describe blocks to create sections for different parts of my code. Inside, it blocks handle individual test cases. For setup and cleanup, beforeEach and afterEach functions save me from repeating the same code. Here's a simple example from a project where I tested a calculator.
describe('Calculator operations', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
afterEach(() => {
calculator = null;
});
it('adds positive numbers correctly', () => {
const result = calculator.add(5, 3);
expect(result).toBe(8);
});
it('handles decimal numbers in addition', () => {
const result = calculator.add(2.5, 1.5);
expect(result).toBe(4.0);
});
});
This structure keeps tests neat. If I need to test async code, like fetching data from an API, Jest handles that smoothly. I once built a user service that made network calls, and testing it was tricky until I used async and await.
describe('User data fetching', () => {
it('retrieves user details successfully', async () => {
const userService = new UserService();
const user = await userService.getUser(123);
expect(user.id).toBe(123);
expect(user.email).toContain('@');
});
it('manages errors when user is not found', async () => {
const userService = new UserService();
await expect(userService.getUser(999)).rejects.toThrow('User not found');
});
});
Mocking is another technique I rely on heavily. It lets me replace parts of the code that are hard to test, like database calls or external APIs. By using jest.fn(), I can create fake functions that simulate real behavior. In one project, I had a payment service that depended on an external gateway. Mocking it made tests predictable and fast.
describe('Payment processing', () => {
let paymentService;
let gatewayMock;
beforeEach(() => {
gatewayMock = {
charge: jest.fn()
};
paymentService = new PaymentService(gatewayMock);
});
it('calls the gateway with correct amount', async () => {
gatewayMock.charge.mockResolvedValue({ success: true });
await paymentService.processPayment(100, 'USD');
expect(gatewayMock.charge).toHaveBeenCalledWith(100, 'USD');
});
it('handles gateway failures gracefully', async () => {
gatewayMock.charge.mockRejectedValue(new Error('Network error'));
await expect(paymentService.processPayment(50, 'USD')).rejects.toThrow('Payment failed');
});
});
Assertion methods are the heart of testing—they check if the code does what it should. Jest offers many options, like toBe for exact matches or toEqual for objects. I often use toContain for arrays or strings. Early on, I missed edge cases, but now I combine multiple checks for thoroughness.
describe('Array operations', () => {
it('finds an item in the list', () => {
const fruits = ['apple', 'banana', 'orange'];
expect(fruits).toContain('banana');
expect(fruits.length).toBe(3);
});
it('validates object properties', () => {
const person = { name: 'Alice', age: 30 };
expect(person).toEqual({ name: 'Alice', age: 30 });
expect(person.age).toBeGreaterThan(18);
});
});
Snapshot testing has saved me from accidental UI changes. It takes a "picture" of a component's output and compares it to a saved version. I use this with React components to catch visual regressions. When I update a component, I review the snapshot changes to ensure they're intentional.
describe('Button component', () => {
it('renders correctly', () => {
const button = renderer.create(<Button label="Click me" />);
const tree = button.toJSON();
expect(tree).toMatchSnapshot();
});
});
For inline snapshots in smaller tests, Jest can generate them on the fly.
it('formats a date string properly', () => {
const formattedDate = formatDate('2023-10-05');
expect(formattedDate).toMatchInlineSnapshot(`"October 5, 2023"`);
});
Code coverage tells me how much of my code is tested. I set thresholds in Jest to fail builds if coverage drops below a certain level. In my experience, aiming for 80-90% coverage is practical—it highlights critical areas without wasting time on trivial code. I once neglected coverage and missed a bug in error handling; now I check reports regularly.
// In package.json, I add coverage settings
{
"scripts": {
"test": "jest --coverage"
},
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 85,
"lines": 80,
"statements": 80
}
}
}
}
Parameterized tests let me run the same test with different inputs. This is great for functions with multiple scenarios. I use test.each to provide arrays of test cases, which makes my tests concise and comprehensive.
describe('Password strength checker', () => {
test.each([
['abc123', false],
['StrongPass1!', true],
['', false],
['a', false]
])('checks %s and returns %s', (password, expected) => {
expect(isPasswordStrong(password)).toBe(expected);
});
});
I also generate test data programmatically for complex cases, like testing a function that handles various user roles.
const userRoles = ['admin', 'editor', 'viewer'];
userRoles.forEach(role => {
it(`allows ${role} to access dashboard`, () => {
const hasAccess = checkAccess(role);
expect(hasAccess).toBe(true);
});
});
Organizing tests across a large codebase can be challenging. I structure test files next to the source code, using .test.js suffixes. This way, when I change a file, I know where to find its tests. I also group tests into suites for better management. Custom matchers have been a game-changer for me—they make assertions more readable and reusable.
// Custom matcher for checking numbers in a range
expect.extend({
toBeInRange(received, min, max) {
const pass = received >= min && received <= max;
return {
message: () => `expected ${received} to be between ${min} and ${max}`,
pass
};
}
});
describe('Temperature converter', () => {
it('converts Celsius to Fahrenheit within range', () => {
const fahrenheit = convertCtoF(0);
expect(fahrenheit).toBeInRange(31, 33); // 0°C is 32°F
});
});
Continuous integration automates testing, giving quick feedback on changes. I set up hooks to run tests before commits and use CI services for pull requests. Parallel execution speeds things up in big projects. Once, a small change broke multiple features, but CI caught it early, saving hours of debugging.
// Example pre-commit hook using Husky
// In package.json
{
"scripts": {
"precommit": "jest --findRelatedTests"
}
}
These techniques have transformed how I write code. Testing is no longer a chore but a confidence booster. By structuring tests well, using mocks, and leveraging Jest's features, I catch issues early and refactor fearlessly. Start with one technique, like better test organization, and gradually incorporate others. You'll find that good testing habits make your code more reliable and your development process smoother.
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | Java Elite Dev | Golang Elite Dev | Python Elite Dev | JS Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva
Top comments (0)