Mocha Mastery
Mocha is the “Swiss Army Knife” of JavaScript testing. Unlike Jest, which is an “Framework” (Runner + Assertions + Mocks), Mocha is primarily a Test Runner. This gives you the flexibility to choose your own tools, but requires more setup.Ecosystem:
- Runner: Mocha
- Assertions: Chai (Expect/Should)
- Mocks: Sinon
- Coverage: NYC (Istanbul)
1. The Mocha Runtime
Understanding how Mocha executes files is crucial for avoiding “it works on my machine” errors.Discovery Phase vs Execution Phase
When you runmocha:
- Discovery: Mocha scans directories.
- Parsing: It
require()s every test file.describe()blocks execute IMMEDIATELY at this stage. - Execution:
it(),before(),after()hooks execute.
before() hooks.
Dynamic Test Generation
Sincedescribe runs during parsing, you can generate tests dynamically:
2. Advanced Assertions (Chai)
Chai offers three styles.expect is the most modern and readable.
Deep Equality vs Strict Equality
Chaining
Chai chains are readable English.Plugins
Chai functionality is extended via plugins.chai-as-promised:await expect(promise).to.be.rejectedWith(Error)chai-http: Integration testing for API endpoints.
3. Spies, Stubs, and Mocks (Sinon)
Sinon is powerful but confusing. Here’s exactly when to use what.1. Spies (Observation)
Use when you don’t want to change behavior, just check if called.2. Stubs (Control)
Use when you want to force behavior (e.g., force DB error).3. Mocks (Expectation Manager)
Combines spying and verification. “This method MUST be called X times”.4. Sinon Sandbox
Manually restoring every stub is painful and error-prone. Use Sandboxes.4. Async & Time
Handling Timeouts
Default timeout is 2000ms. For slow integration tests:- Global Command:
mocha --timeout 10000 - Per Suite:
this.timeout(5000)inside describe. - Per Test:
this.timeout(5000)inside it.
Fake Timers (Sinon)
Don’t wait 5 seconds in a unit test.5. Root Hooks & Global Fixtures
For setting up Database connections or Web Servers once for the entire suite. Create a filetest/hooks.js:
mocha --require test/hooks.js
6. Code Coverage (NYC)
Istanbul (NYC) instruments code to count line execution. Gatekeeping: Fail CI if coverage drops..nycrc or nyc.config.js.
7. Running in the Browser (Karma vs Headless)
Mocha runs in Node.js by default. To test frontend DOM logic:Option 1: JSDOM (Fast, Simulated)
Simulate browser in Node.Option 2: Karma (Real Browser)
Karma launches Chrome, injects Mocha + tests, reporting back to terminal. Complex setup, but 100% accurate.Option 3: Playwright/Puppeteer
Use Mocha just to drive Playwright automation (See Playwright course).8. Common Pitfalls & Debugging
The Arrow Function Context Trap
The Arrow Function Context Trap
Symptom:
TypeError: this.timeout is not a function or this.retries fails.
Cause: Arrow functions () => {} bind this lexically (from the parent scope), ignoring Mocha’s context.
Fix: Always use function() syntax when accessing this.Mixing 'done' and Promises
Mixing 'done' and Promises
Symptom:
Error: Resolution method is overspecified.
Cause: Your test accepts a done callback BUT also returns a Promise (async function). Mocha doesn’t know which one to listen to.
Fix: Use one or the other, never both.Global Variable Leaks
Global Variable Leaks
Symptom: Test B fails only when Test A runs.
Cause: Modifying a global variable (or module-level variable) without resetting it.
Fix: Use
afterEach() to clean up state or Sinon Sandboxes to restore mocks.9. Interview Questions
Explain the difference between TDD, BDD, and Exports interfaces in Mocha.
Explain the difference between TDD, BDD, and Exports interfaces in Mocha.
- BDD:
describe(),it(),before(). Reads like behavior specs. (Most popular). - TDD:
suite(),test(),setup(). Closer to traditional unit testing. - Exports: Exporting an object where keys are descriptions. values are functions.
Mocha supports all of them via the
--uiflag (default isbdd).
Why do we need a separate assertion library like Chai?
Why do we need a separate assertion library like Chai?
Mocha is unopinionated. It only handles running the tests and reporting results. It does not know how to check if
2 + 2 = 4.
You can use Node’s built-in assert module, but Chai provides expressive, legible assertions (expect(x).to.be.true) that make failure messages easier to read.How does Mocha handle asynchronous code differently from synchronous code?
How does Mocha handle asynchronous code differently from synchronous code?
If a test function takes a parameter (conventionally named
done), Mocha pauses execution until done() is called.
If a test function returns a Promise, Mocha waits for resolution/rejection.
If neither, it assumes synchronous execution and passes immediately if no error is thrown.