Getting started with JavaScript unit testing through the Saleh Hazem approach
I ran across a set of materials by someone going by Saleh Hazem around two years ago while looking for a straightforward way to teach JavaScript unit testing without a lot of theoretical padding. The content was structured differently than most tutorials you find on the web. Instead of starting with test runners and assertion libraries, Saleh Hazem starts with the actual problem most developers face: writing code that is impossible to test because everything is coupled together. The core idea behind the JavaScript Unit Testing Saleh Hazem framework is simpler than the typical course structure you see elsewhere. You begin by writing a small utility function that does exactly one thing. Then you write a failing test for it. Only then do you make it pass. It sounds obvious but the way Saleh Hazem walks through dependency mocking and side effect isolation in a way that actually works in browser environments is where most people drop off. I remember trying to apply the pattern to a project that was pulling real data from a REST API during tests. Every single test was hitting the network, taking 3 to 4 seconds each, and failing intermittently when the backend decided to be slow. I spent about half a day debugging flaky tests before realizing I was mocking at the wrong layer. The Saleh Hazem method actually calls for mocking the service layer before the controller or component layer even knows about it. I switched to wrapping the fetch calls inside a thin service object, then spied on that object instead of trying to intercept XMLHttpRequests. Tests went from 4 seconds to under 80 milliseconds each.
The tooling Stack that works cleanly with this approach is Jest or Vitest paired with MSW for network mocking and a minimal setup file. Don't bother with complex test environments unless you are testing DOM behavior. For business logic, a basic setup is enough.
Setting up the environment
Create a fresh project directory. Initialize it with npm init -y. Install Jest or Vitest depending on whether your project uses bundlers like Vite or Webpack. Add ts-jest if you are working with TypeScript. Install MSW if your tests touch network requests. That is roughly it for the foundation. I used to overcomplicate my setup files by importing third party polyfills and custom matchers into every test. It added 2 seconds to cold start times. I removed everything except what each test file actually needed and got the startup time down to under half a second.
Get the Full Details

Writing the first test correctly
Start with something trivial like a function that formats a date string. Do not write the implementation first. Write the test expecting the format you want. Use Jest's expect or Vitest's assert syntax. The test should fail. The red state is normal and not a sign that something is broken. Here is what a basic test looks like in practice: const { formatDate } = require('./utils');
test('formats ISO date into DD/MM/YYYY', () => { expect(formatDate('2025-06-15')).toBe('15/06/2025'); });
Write the smallest implementation that passes. Then refactor. This cycle keeps your code testable by design rather than testable by accident.

Mocking real world dependencies
Most unit testing failures come from dependencies that behave unpredictably. Network calls, timers, random number generators, and file system access are the usual suspects. Saleh Hazem covers this by showing how to isolate these using jest.mock or vi.stubGlobal. The key insight is that you mock at the point of import, not inside the function itself. I once had a function that depended on crypto.getRandomValues for generating IDs. Mocking the global crypto object directly caused issues in Node 18 because the binding was tightly coupled to the runtime. The workaround was extracting the random generation into a small module and mocking that module instead. It took one extra file but eliminated the flakiness permanently.
Common pitfalls I have seen
People often write integration tests and call them unit tests. A true unit test should have zero external dependencies. If it touches a database, reads a file, or makes a network request, it is no longer a unit test. Another mistake is asserting on implementation details rather than behavior. Testing that a function calls console.log five times tells you nothing about whether the output is correct. Assert on the return value instead. A less obvious pitfall is overusing beforeEach. If you reset mocks before every test but forget to restore them, leftover state from a previous test can cause failures that make no sense at first glance. Always use afterEach to clean up any stubs or spies you create. jest.restoreAllMocks() is useful but it will also restore native mocks you did not intend to touch. Prefer jest.clearAllMocks() when you only want to reset call history.
What the approach does not handle well
The Saleh Hazem materials focus heavily on pure logic and service layer testing. They do not go deep into component testing with React or Vue, nor do they cover end to end testing strategies. If you need to validate that a full React component renders correctly after a user click, you will need to supplement this with Testing Library. The unit testing foundation is solid but it stops at the boundary of the service layer. That is a limitation to be aware of if your work involves heavy frontend code. Another gap is coverage of asynchronous patterns beyond basic promises. Real world code uses race conditions, retries with exponential backoff, and concurrent async operations. The framework treats these as edge cases rather than core topics. I ended up learning about race condition testing from separate resources after finishing the main content.

Where to find the material
The JavaScript Unit Testing Saleh Hazem materials are available as a collection of written guides and code examples. Search for the author's name along with the topic on developer blog platforms and GitHub. There is no single official download portal, so check both the author's public repositories and any mirrored README files that include the full curriculum structure. The source code examples are usually hosted alongside the articles rather than in a separate package. Start each new module by writing the test first. Keep the test file next to the source file. Name it the same with a .test suffix. Run only that file with npx jest path/to/file.test.js. Do not run the entire suite while developing a single feature. Full suite runs add unnecessary waiting time when you are iterating fast. Once the feature is complete, run the full suite to catch regressions. I cut my daily feedback loop from about 90 seconds down to 3 seconds by restricting test runs to the relevant file during development and running the full suite only at merge time. The tradeoff is that you might miss a regression until the full run, but the speed gain during active coding makes it worth it in most teams.
Final notes on effectiveness
The approach works well for teams that are new to testing or have legacy JavaScript codebases that need incremental coverage. It does not replace integration testing or contract testing. It also will not fix a codebase that is fundamentally untestable due to tight coupling. No framework can solve that without refactoring. If your architecture prevents isolation, focus on small boundary extraction before applying the testing patterns.