🌍 ExpressJS · subject
ExpressJS Testing and Debugging in ExpressJS Syllabus
The Testing and Debugging in ExpressJS syllabus for ExpressJS in full: 3 chapters and 7 topics, every one listed below, plus 50 Testing and Debugging in ExpressJS flashcards with answers.
Testing and Debugging in ExpressJS syllabus: all 3 chapters
Expand any chapter to see its topics and sub-topics.
-
Unit Testing
3 topics- Using Mocha and Chai
- Testing Middleware
- Testing Routes
-
Integration Testing
2 topics- Using Supertest
- Mocking Dependencies
-
Debugging Techniques
2 topics- Using Debug Module
- Using Node.js Inspector
ExpressJS Testing and Debugging in ExpressJS flashcards — 50 questions with answers
What are Mocha and Chai, and what role does each play in testing an ExpressJS application?
Mocha is a JavaScript test framework that runs test suites and provides the structure (describe/it blocks, hooks, async support). Chai is a standalone assertion library used inside those tests to express expectations. Mocha runs the tests; Chai checks that values are correct.
Which Mocha functions define a test suite versus an individual test case?
describe() defines a suite (a grouping of related tests), and it() defines an individual test case. Suites can be nested; each it() contains one or more assertions.
List the four Mocha lifecycle hooks and when each runs.
before() runs once before all tests in a block; after() runs once after all tests; beforeEach() runs before every individual test; afterEach() runs after every individual test.
What are Chai's three assertion styles?
assert (assert.equal(a,b)), expect (expect(a).to.equal(b)), and should (a.should.equal(b)). expect and should are BDD-style chainable; assert is TDD-style.
In Chai's expect style, how do you assert that a value strictly equals 200 versus is deeply equal to an object?
Use expect(res.status).to.equal(200) for strict (===) equality, and expect(obj).to.deep.equal({a:1}) for deep structural equality of objects/arrays.
How do you handle asynchronous code in a Mocha test that returns a Promise?
Return the Promise from the test callback (Mocha waits for it to resolve/reject), or use an async function and await the Promise. Alternatively call the done callback for callback-style async.
What is the purpose of the done callback in a Mocha test, and what causes a timeout error?
done signals completion of an asynchronous test. Mocha waits until done() is called; if it is never called (or the async op exceeds the default 2000 ms timeout), Mocha fails the test with a timeout error.
What command-line flag sets a custom timeout in Mocha, and what is the default timeout?
The default timeout is 2000 ms. Override it with --timeout <ms> on the CLI, or per-test with this.timeout(ms) inside a non-arrow test function.
Why should you avoid arrow functions when you need this.timeout() or this.skip() in Mocha tests?
Arrow functions do not bind their own this, so Mocha's context (with .timeout(), .skip(), .retries()) is unavailable. Use regular function() expressions to access the Mocha context.
What is Supertest and what problem does it solve when testing Express apps?
Supertest is a library for high-level HTTP assertion testing. It wraps your Express app, spins up an ephemeral server, sends real HTTP requests, and lets you assert on responses without manually starting/stopping a server or hardcoding ports.
Write a minimal Supertest request that GETs '/users' and asserts a 200 status.
request(app).get('/users').expect(200); where request is require('supertest'). Return this from the test or await it so Mocha waits for completion.
In Supertest, how do you send a JSON POST body and set a request header?
request(app).post('/users').set('Content-Type','application/json').send({ name: 'Ann' }).expect(201). .send() sets the body and .set() sets headers.
What must you export from your Express module so Supertest can test it without the server already listening?
Export the app instance (module.exports = app) rather than the result of app.listen(). Supertest binds to the app and manages an ephemeral port itself.
How do you chain multiple .expect() assertions in Supertest, and what are the valid argument forms?
You can chain multiple .expect() calls. Forms: expect(status), expect(status, body), expect(field, value) for a header, expect(body) for the response body, or expect(fn) with a custom assertion function that throws on failure.
How do you access the full response object at the end of a Supertest chain to run custom Chai assertions?
Use .then(res => { ... }) or await the chain: const res = await request(app).get('/'); then assert on res.status, res.body, res.headers with Chai.
When testing an Express route that returns JSON, how do you assert both the status code and a property in the response body using Supertest and Chai?
const res = await request(app).get('/api/item/1'); expect(res.status).to.equal(200); expect(res.body).to.have.property('name','Widget'). res.body is the parsed JSON.
What is Express middleware, and what three arguments does a standard (non-error) middleware function receive?
Middleware is a function that runs during the request-response cycle with access to req, res, and next. It receives (req, res, next); calling next() passes control to the next middleware.
How many arguments does Express error-handling middleware take, and in what order?
Four: (err, req, res, next). Express identifies error-handling middleware by its four-parameter signature; err is the error passed via next(err).
To unit-test a piece of Express middleware in isolation, what do you typically provide as its arguments?
Mock/stub objects for req and res (with the methods the middleware calls, e.g. res.status, res.json) and a spy/stub for next. Then assert that the middleware called next(), set the right status, or attached properties to req.
How can you verify that a middleware called next() exactly once with no error?
Pass a spy as next (e.g. sinon.spy()), invoke the middleware, then assert expect(next.calledOnce).to.be.true and expect(next.firstCall.args[0]).to.be.undefined (no error argument).
Show the other 30 Testing and Debugging in ExpressJS flashcards
What is the difference between unit-testing middleware in isolation and integration-testing it through the app?
Isolation unit testing calls the middleware directly with mock req/res/next to test its internal logic quickly. Integration testing mounts the middleware on the app and uses Supertest to send real requests, verifying it behaves correctly within the full stack.
When testing Express routes, what are the two main approaches and their trade-offs?
(1) Integration/HTTP testing with Supertest — realistic, exercises the whole stack, slower. (2) Unit testing the route handler function directly with mock req/res — fast and isolated, but does not verify routing, middleware order, or parsing.
How do you test a route that requires an authenticated user with Supertest?
Attach the auth credential to the request, e.g. .set('Authorization','Bearer '+token) or .set('Cookie', sessionCookie), then assert the protected route returns 200; also assert it returns 401/403 when the credential is missing.
How do you preserve and reuse a session cookie across Supertest requests?
Use an agent: const agent = request.agent(app). The agent persists cookies between requests, so a login POST followed by a protected GET on the same agent stays authenticated.
What is 'mocking a dependency' and why is it important in Express route/service tests?
Mocking replaces a real dependency (database, external API, module) with a controlled fake so the test is fast, deterministic, and isolated from external state. It lets you test your code's logic without network calls or a live database.
Distinguish between a stub, a spy, and a mock in Sinon terms.
A spy records calls to a function without changing behavior. A stub replaces a function with programmed behavior (return values, thrown errors). A mock is a stub with pre-set expectations that it verifies were met. All help isolate the unit under test.
How would you stub a model method (e.g. User.find) so an Express handler test does not hit the real database?
Use sinon.stub(User,'find').resolves([{id:1}]) (or .returns/.rejects) so the handler receives the fake result. Restore it afterward with .restore() (often in afterEach) to avoid leaking the stub into other tests.
What library allows you to intercept and mock outgoing HTTP requests to external APIs in Node tests?
nock. It intercepts HTTP(S) requests at the network layer, letting you define expected requests and canned responses, so tests of code that calls external APIs run without real network traffic.
Why should you always restore stubs/spies after each test, and what Sinon feature simplifies this?
Unrestored stubs persist and corrupt later tests (leaking fakes, false passes/failures). Use a sinon.createSandbox() (or sinon.sandbox) and call sandbox.restore() in afterEach to reset all fakes in one call.
What is the Debug module and why use it instead of console.log in Express apps?
debug is a small utility that provides namespaced, conditional logging. Unlike console.log, its output is off by default and enabled selectively via the DEBUG environment variable, so you get targeted diagnostics without cluttering production output or editing code.
How do you create and use a namespaced debugger with the debug module?
const debug = require('debug')('app:server'); then call debug('listening on %d', port). 'app:server' is the namespace; the message only prints when that namespace is enabled.
How do you enable debug output for a specific namespace when running an Express app?
Set the DEBUG environment variable, e.g. DEBUG=app:server node app.js. Use DEBUG=app:* to enable all namespaces under 'app', or DEBUG=* for everything.
How do you enable multiple debug namespaces but exclude one?
Comma-separate namespaces and prefix an exclusion with '-', e.g. DEBUG=app:*,-app:db enables all app namespaces except app:db. Wildcards (*) match any characters.
What formatting placeholders does the debug module support, and give two examples.
printf-style formatters: %s (string), %d (number), %j (JSON), %o/%O (object inspection), %% (literal percent). Example: debug('user %s id %d', name, id).
In the Express generator's default app, which debug namespace is commonly used and where?
The generated bin/www uses require('debug')('<appname>:server') to log server startup and errors (e.g. the listening event and error handler), enabled via DEBUG=<appname>:*.
What is the Node.js Inspector, and how do you start an Express app with it enabled?
The Node.js Inspector is a built-in debugger exposing the V8 Inspector Protocol over WebSocket, usable with Chrome DevTools or VS Code. Start it with node --inspect app.js (or node --inspect-brk app.js to break before the first line).
What is the difference between --inspect and --inspect-brk?
--inspect starts the inspector and runs the program immediately (you must have set breakpoints or attach quickly). --inspect-brk starts the inspector and pauses on the first line of code, letting you attach a debugger before anything executes.
What default host and port does node --inspect use, and why is binding to 0.0.0.0 discouraged?
It listens on 127.0.0.1:9229 by default. Binding to a public interface (--inspect=0.0.0.0:9229) is a security risk because the inspector allows arbitrary code execution; anyone who can reach the port can control the process.
How do you connect Chrome DevTools to a running Node inspector session?
Open chrome://inspect in Chrome and click the target under 'Remote Target', or navigate to the devtools:// URL printed to the console when the inspector starts. You can also attach VS Code's Node debugger.
What is a breakpoint, and how can you set one directly in Node source code without a GUI?
A breakpoint pauses execution at a specific line so you can inspect state. You can hardcode one with the debugger; statement, which halts execution there whenever an inspector is attached.
Which Chai assertion checks that a function throws an error, and how is it written?
expect(fn).to.throw() (optionally expect(fn).to.throw(TypeError, /message/)). Pass the function itself (not its invocation) so Chai can call it and catch the thrown error.
How do you assert on the shape of a JSON array response body (e.g. length and element property) with Chai?
expect(res.body).to.be.an('array').with.lengthOf(3); and expect(res.body[0]).to.have.property('id'). Chained language assertions verify type, length, and member properties.
What npm script convention runs Mocha tests, and how is Mocha typically invoked?
Add "test": "mocha" (optionally with a glob like "mocha './test/**/*.spec.js'") to package.json scripts, then run npm test. Mocha by default looks in the ./test directory.
How do you test that an Express route responds with the correct Content-Type header using Supertest?
request(app).get('/api').expect('Content-Type', /json/).expect(200). Passing a header name and a regex/string to .expect() asserts on that response header.
When mocking a database in a route integration test, why is dependency injection preferable to stubbing globally?
Dependency injection (passing the db/service into the handler or module) makes the dependency explicit and lets you supply a fake in tests without patching global module internals. It reduces coupling, avoids fragile stub-restore bookkeeping, and makes tests more reliable.
What is the purpose of proxyquire (or rewire) in Express testing?
proxyquire lets you require a module while overriding its own require() calls, so you can inject mock versions of that module's dependencies. It is used when a dependency is required internally and cannot easily be stubbed or injected.
How can you test error-handling middleware end-to-end with Supertest?
Trigger a route that calls next(err) (or throws), then assert the response reflects the error handler's output, e.g. request(app).get('/boom').expect(500).expect(res => expect(res.body.error).to.exist). This verifies the error middleware is wired correctly.
What Node.js CLI command opens the built-in command-line debugger (not DevTools), and name two of its stepping commands.
node inspect app.js opens the built-in CLI debugger. Stepping commands include cont (c) to continue, next (n) to step over, step (s) to step into, out (o) to step out, and repl to inspect variables.
Why can leaving app.listen() calls in modules under test cause problems, and what is the fix?
Calling app.listen() at import time binds a port; multiple test files can collide (EADDRINUSE) or leave open handles that prevent Mocha from exiting. Fix: export the app and only call listen() in a separate entry point (e.g. bin/www) or let Supertest manage the ephemeral port.
Compare the debug module and the Node.js Inspector as diagnostic tools for Express.
The debug module gives lightweight, always-in-code, namespace-filtered log output controlled by DEBUG at runtime — good for tracing flow in dev and production. The Node.js Inspector is an interactive debugger with breakpoints, step execution, and live variable inspection via DevTools/VS Code — good for deep, stateful investigation during development.
ExpressJS Testing and Debugging in ExpressJS FAQ
What is in the ExpressJS Testing and Debugging in ExpressJS syllabus?
ExpressJS Testing and Debugging in ExpressJS is split into 3 chapters: Unit Testing, Integration Testing and Debugging Techniques. Together they contain 7 topics.
How many chapters are in ExpressJS Testing and Debugging in ExpressJS?
3 chapters, containing 7 topics. The largest chapter is Unit Testing, with 3 topics.
Are there flashcards for ExpressJS Testing and Debugging in ExpressJS?
Yes — a 50-card Testing and Debugging in ExpressJS deck, printed in full in the flashcards section of this page and available in the Examius app with spaced repetition. The free plan covers one exam of your choice, including its flashcards; Examius Pro, a one-time purchase, unlocks every other exam and deck.