Resource library

QA Interview

JavaScript Promises Interview Questions for QA Automation (2026)

JavaScript promises interview questions QA automation candidates need, with event loop, async testing, error handling, concurrency, and coding answers.

22 min read | 3,872 words

TL;DR

Strong candidates explain Promise state, microtask ordering, chaining, async/await, rejection propagation, and concurrency in terms of reliable tests. They can also diagnose missing returns, swallowed errors, premature completion, and unsafe parallel work.

Key Takeaways

  • A Promise represents one eventual result and can settle only once.
  • Microtasks run after the current stack and before the next timer task.
  • Return or await every asynchronous operation that controls a test result.
  • Choose Promise.all, allSettled, any, or race from the required failure semantics.
  • Use finally for cleanup while preserving the original outcome.
  • Reject with Error objects and assert both the rejection type and meaningful details.
  • Control concurrency when test data, APIs, or infrastructure have capacity limits.

These javascript promises interview questions qa automation engineers face are designed to reveal whether asynchronous test code is trustworthy. A strong answer connects the Promise specification to practical outcomes: when a test finishes, how a failure reaches the runner, whether cleanup preserves the original error, and whether concurrent work shares unsafe state.

Use this hub to practice concise explanations and runnable Node.js examples. For a deeper lesson before the interview, review async and await in tests and Promises and error handling in QA. Then use the QA practice area to answer aloud under time pressure.

TL;DR

Topic Interview-ready point QA consequence
State A Promise is pending, then fulfilled or rejected once A late second callback cannot change the result
Scheduling Handlers enter the microtask queue A zero-delay timer normally runs later
Chaining Each then, catch, or finally creates a new Promise Missing return breaks failure and completion tracking
Errors Throws in a handler become rejections The runner fails only if it observes that chain
Concurrency Combinators differ in result and failure behavior Choose semantics before optimizing runtime
Cleanup finally runs for either outcome Teardown can preserve or replace the original result

The best response names the rule, applies it to test execution, and gives one failure mode. That is more credible than reciting syntax without explaining what the runner observes.

1. JavaScript Promises Interview Questions QA Automation Fundamentals

Q: What is a JavaScript Promise?

A Promise is an object representing the eventual completion or failure of one asynchronous operation. It starts pending and settles as fulfilled with a value or rejected with a reason. A test runner can await that object to know when work finishes. The Promise is not the operation itself, so cancelling the Promise reference does not automatically cancel an HTTP request or timer.

Q: What are the three Promise states?

The states are pending, fulfilled, and rejected. Fulfilled and rejected are collectively called settled, and settlement is permanent. JavaScript does not expose a standard synchronous property for inspecting a Promise's state. In test code, observe the result by awaiting it or attaching a handler instead of polling internal fields.

Q: What is the difference between fulfilled and resolved?

Fulfilled means the Promise has a final successful value. Resolved means its fate has been locked to another value or thenable, which can still be pending or ultimately rejected. For example, resolving an outer Promise with a pending fetch Promise makes the outer Promise follow it. This distinction explains why calling resolve(innerPromise) does not guarantee immediate fulfillment.

Q: Can a Promise settle more than once?

No. The first accepted fulfillment or rejection determines the outcome, and later calls are ignored. This guards against a callback-style API accidentally invoking both success and failure paths. It does not excuse a broken wrapper, because repeated callbacks can still cause other side effects that tests should detect.

Q: Is a Promise lazy?

The executor passed to new Promise runs synchronously during construction. Promise handlers are deferred, but creating the Promise can start work immediately. If a test needs lazy execution, wrap creation in a function and call that function at the intended time. Confusing handler deferral with lazy startup can launch setup before fixtures are ready.

const lazyRequest = () => Promise.resolve('started');
console.log('before');
lazyRequest().then(console.log);
console.log('after');
// before, after, started

Run it with node promises-demo.mjs and verify that started prints last.

2. Event Loop and Microtask Ordering

Q: Where do Promise callbacks run?

Callbacks registered by then, catch, and finally are queued as microtasks after their Promise becomes ready. They never interrupt the currently executing JavaScript stack. The runtime drains microtasks before moving to the next ordinary task such as a timer callback. This ordering matters when a test mixes UI events, timers, and resolved Promises.

Q: Which runs first, a resolved Promise or setTimeout(fn, 0)?

After the current stack completes, the Promise reaction normally runs before the timer because microtasks are drained before the next timer task. Zero milliseconds is a minimum scheduling threshold, not an immediate call. An interview answer should mention the current synchronous statements first. Environment-specific queues can add detail, but they do not reverse this basic example.

console.log('A');
setTimeout(() => console.log('timer'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('B');
// A, B, promise, timer

Save the block as ordering.mjs, run node ordering.mjs, and compare all four lines in order.

Q: What is microtask starvation?

Microtask starvation occurs when code continually queues more microtasks, delaying timers, I/O callbacks, and rendering opportunities. A recursive Promise chain can therefore make an automation process appear frozen even without a blocking loop. Avoid unbounded self-scheduling and introduce an appropriate task boundary or bounded iteration. A timeout cannot fire while its task is starved.

Q: Does await block the JavaScript thread?

No. await pauses only the surrounding async function and lets the runtime process other work. The continuation is scheduled asynchronously when the awaited value settles. CPU-heavy synchronous code before or after the await still blocks the thread. In a worker running many tests, replacing computation with await does not make that computation nonblocking.

Q: Why can logging change the apparent order of a flaky test?

Logging adds work and can alter timing, I/O pressure, or observation points without repairing synchronization. The underlying race remains if the test waits for elapsed time instead of an observable application state. Capture timestamps and state transitions, then wait on the actual condition. Treat a failure that disappears with logging as evidence of a timing dependency.

3. Chaining, Values, and Thenables

Q: What does then() return?

Every then() call returns a new Promise. Its result depends on the handler: a returned value fulfills the new Promise, a thrown error rejects it, and a returned Promise or thenable is adopted. This is why a chain can transform both timing and data. Store or return the final chain that represents the whole test operation.

Q: Why must a Promise be returned from inside then()?

Returning connects the nested operation to the outer chain. Without the return, the next handler receives undefined and may run before the nested operation finishes. A later rejection can become unhandled instead of failing the test. This missing-return bug often creates false passes and teardown races.

const createUser = () => Promise.resolve({ id: 42 });
const loadUser = id => Promise.resolve({ id, active: true });

const result = await createUser()
  .then(user => loadUser(user.id))
  .then(user => user.active);

if (result !== true) throw new Error('User should be active');
console.log('verified');

Run node chain.mjs on a current Node.js release and expect verified.

Q: What happens when a then() handler returns a plain value?

The Promise returned by then() fulfills with that value. The following handler receives it asynchronously. This makes chains useful for normalization, such as converting an API response into the specific field an assertion needs. Returning a value is different from merely assigning it to an outer variable, which creates timing-sensitive shared state.

Q: What is a thenable?

A thenable is an object with a callable then property, even if it is not a native Promise. Promise resolution assimilates thenables so libraries can interoperate. Poorly behaved custom thenables can call handlers strangely, so native Promises are safer in application and test code. await also adopts thenables rather than returning the object unchanged.

Q: How do you run steps sequentially for data-dependent setup?

Use await in order when each result supplies the next input. Parallel combinators are incorrect if user creation must finish before an order can reference that user's ID. Keep the dependency visible in local variables. If cleanup is required, register identifiers immediately after each successful creation so partial setup can be reversed.

4. Rejections and Error Propagation

Q: How does an error move through a Promise chain?

A thrown exception in an executor or reaction becomes a rejection of the resulting Promise. Rejection skips fulfillment handlers until a matching rejection handler or catch is found. If catch returns normally, the chain recovers and becomes fulfilled. A test fails only when the runner awaits or returns a chain whose rejection remains observable.

Q: Should code reject with a string or an Error?

Reject with an Error instance or a meaningful subclass. Errors carry a stack, name, message, and optional cause, which make CI diagnosis far better than a bare string. Tests can assert the error class and selected properties without coupling to an entire stack. Preserve sensitive response content according to the project's logging policy.

Q: What is an unhandled rejection?

It is a rejected Promise that has no rejection handler when the runtime checks it. It commonly signals forgotten await, forgotten return, or fire-and-forget work. Runtime policy and test-runner behavior can differ, so never depend on a warning to fail a test. Make ownership explicit and await all work that affects correctness.

Q: What is wrong with .catch(console.log) in a test?

console.log returns undefined, so the catch handler converts the rejection into a fulfilled chain. The test may pass after printing the very failure it should report. Log and rethrow if extra context is necessary, or let the runner display the original rejection. Adding context with Error cause can retain the underlying failure.

Q: How should a test verify an expected rejection without swallowing unexpected success?

Use the test runner's rejection assertion when available. In plain JavaScript, await the operation inside try, throw if it unexpectedly succeeds, and inspect the caught error carefully so the sentinel is not mistaken for the expected failure. Assert stable properties such as error type, code, and a meaningful message fragment. Avoid snapshots of volatile stacks.

import assert from 'node:assert/strict';

const rejectLogin = async () => {
  const error = new Error('Invalid credentials');
  error.code = 'AUTH_INVALID';
  throw error;
};

await assert.rejects(rejectLogin, error => {
  assert.equal(error.code, 'AUTH_INVALID');
  return true;
});
console.log('rejection verified');

Run node rejection.mjs and expect rejection verified.

5. Async and Await in Test Code

Q: What does an async function return?

An async function always returns a Promise. A plain returned value becomes a fulfilled result, while a thrown error becomes a rejection. Returning an existing Promise causes the async result to adopt its outcome. Therefore a test declared async must still await the operations inside it.

Q: Does await work with non-Promise values?

Yes. JavaScript converts the operand through Promise resolution, so await 7 eventually produces 7. The continuation still yields rather than behaving like a purely synchronous assignment. Awaiting a value that was supposed to be a Promise can conceal a bad mock, so validate mock contracts when timing matters.

Q: Why is array.forEach(async item => ...) dangerous?

forEach ignores callback return values and does not return a Promise for the callbacks. The outer function can finish while assertions or API calls are still running. Use for...of for sequential work or Promise.all(items.map(async item => ...)) for deliberate concurrency. Choose based on isolation and capacity, not brevity.

Q: When should tests use sequential awaits instead of Promise.all?

Use sequential awaits when operations share mutable data, depend on order, consume a constrained account, or must stop before the next side effect. The slower structure may be the correct representation of the scenario. Use concurrency for independent observations or isolated fixtures. Document any service rate limit that constrains the batch.

Q: How do you avoid forgetting await in a large suite?

Enable TypeScript and lint rules that detect floating Promises, and design helpers to return explicit Promise types. Keep test callbacks async only when they contain awaited work. Code review should trace completion from helper to runner. A local void marker should be reserved for intentionally detached work with its own error handling.

The JavaScript coding interview guide for testers provides more language drills, while the Playwright TypeScript framework tutorial shows these completion rules in a browser suite.

6. Promise Combinators and Concurrency

Q: How does Promise.all behave?

It fulfills with ordered results when every input fulfills and rejects when the first input rejection is observed. Result order follows input order, not completion order. Other operations are not cancelled after rejection and may continue changing state. Use it for independent work only when one failure should fail the combined operation.

Q: When is Promise.allSettled better for QA automation?

Use it when every probe must finish and the report needs every outcome, such as checking several independent service health endpoints. It always fulfills with objects whose status is fulfilled or rejected. The caller must inspect and fail on unacceptable results because the combinator will not reject for them. This makes it suitable for diagnostics, not automatic success criteria.

Q: What does Promise.race do?

It settles with the first input to settle, whether that outcome is fulfillment or rejection. A common use is a timeout boundary, but losing operations keep running unless separately cancelled. If the timed operation can mutate data, pair the timeout with AbortController or another supported cancellation mechanism. Clear timer resources when practical.

Q: What does Promise.any do?

It fulfills with the first fulfilled input and ignores earlier rejections. If every input rejects, it rejects with an AggregateError containing the reasons. It fits redundant read sources where any valid response is sufficient. It is inappropriate when all environments or browsers must pass.

Q: How do you limit Promise concurrency?

Process work in bounded batches or use a vetted concurrency limiter instead of launching thousands of requests at once. The limit should reflect API quotas, database pools, test data isolation, and runner resources. Capture each item's identity with its result so failures remain diagnosable. Concurrency control is a reliability decision, not only a performance tweak.

const check = async id => ({ id, ok: id !== 3 });
const ids = [1, 2, 3, 4];
const results = [];
for (let i = 0; i < ids.length; i += 2) {
  results.push(...await Promise.all(ids.slice(i, i + 2).map(check)));
}
if (results.filter(x => !x.ok).map(x => x.id).join(',') !== '3') {
  throw new Error('Unexpected failures');
}
console.log(results);

Run node batches.mjs and verify that all four IDs appear and only ID 3 has ok: false.

7. Cleanup, Timeouts, and Cancellation

Q: What does finally() receive?

A finally callback receives no fulfillment value or rejection reason. It is intended for outcome-independent cleanup. If it completes normally, the original value or rejection passes through. If it throws or returns a rejected Promise, that new failure replaces the prior outcome.

Q: Is finally the same as then(cleanup, cleanup)?

No. then handlers receive the current value or reason and can easily transform the outcome. finally is transparent when cleanup succeeds and expresses intent more clearly. Use a closure if cleanup needs resource identifiers. Verify cleanup errors separately when masking the original test failure would hinder diagnosis.

Q: How do you implement a cancellable fetch timeout?

Create an AbortController, pass its signal to fetch, and abort it when the timeout expires. Clear the timeout in finally so the timer does not outlive a fast request. Distinguish an abort from an HTTP error because fetch fulfills for HTTP status codes such as 500. Always inspect response.ok or the required status.

async function fetchWithin(url, milliseconds) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), milliseconds);
  try {
    const response = await fetch(url, { signal: controller.signal });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response;
  } finally {
    clearTimeout(timer);
  }
}

// Verification without network access:
await fetchWithin('data:application/json,%7B%22ok%22%3Atrue%7D', 1000);
console.log('fetch completed');

Run node timeout.mjs on Node.js with built-in fetch and expect fetch completed.

Q: Does rejecting a timeout Promise stop the original operation?

No. Promise.race changes what the caller observes but does not cancel the losing input. The operation can later write data, consume a connection, or emit an unhandled rejection. Use a cancellation API supported by that operation and wait for cleanup if isolation requires it. A timeout is a deadline, not magical process control.

Q: How should teardown handle a failed setup?

Track each resource only after creation succeeds, and make deletion safe when the list is empty or partially populated. Run teardown from finally or the runner's fixture lifecycle. Prefer idempotent cleanup so a retry does not create a second failure. If both test and teardown fail, report both when the runner supports aggregated errors.

8. Promise Testing and Mocking Scenarios

Q: How do you test a fulfilled Promise?

Await it and assert the business value, not merely that it resolved. For an API helper, check normalized fields and any contract the caller relies on. Avoid a test that duplicates the implementation by computing the expected value through the same helper. Include boundaries such as an empty valid response where relevant.

Q: How do fake timers interact with Promise microtasks?

Fake-timer APIs primarily control timer scheduling, while Promise reactions use the microtask queue. Advancing a clock may not automatically flush every pending microtask in the way a test assumes. Use the runner's documented async timer method and await it. Do not build a homemade sequence of arbitrary resolved Promises to hide uncertainty.

Q: What makes an asynchronous mock realistic?

It matches the production contract for fulfillment shape, rejection type, and relevant timing boundary. A mock that returns a plain object can let missing awaits pass unnoticed even when production returns a Promise. Include controlled pending and rejection cases for code that handles loading or failures. Keep network protocol behavior in integration tests rather than rebuilding an entire server in mocks.

Q: How do you test retry logic?

Inject the operation and delay mechanism so the test can control attempts without sleeping. Configure a deterministic sequence such as two transient rejections followed by success. Assert call count, final result, and which errors qualify for retry. Add a case proving that permanent validation errors stop immediately.

Q: Why should tests avoid arbitrary sleeps?

A sleep waits for time rather than evidence, so it is both slower on fast runs and unreliable on slow ones. Await a Promise tied to the required state, event, response, or locator condition. Retain a deadline so real failures terminate. This principle applies whether the UI library exposes auto-waiting or the test builds its own polling helper.

For browser-specific application, study Playwright interview questions and the JavaScript API automation framework guide.

9. Debugging Production-Like Failures

Q: Why does a test pass even though an async assertion fails later?

The runner finished before it became responsible for the assertion's Promise. Typical causes are a missing return, missing await, async forEach, or detached event callback. Trace the Promise from assertion back to the test callback. Make the callback return only after every correctness-relevant operation settles.

Q: What causes Cannot read properties of undefined after a chain?

A prior handler may have omitted its return, intentionally returned nothing, or recovered from an error without supplying the expected value. Inspect each transformation boundary rather than adding optional chaining. Optional chaining could hide the broken contract and move the failure farther away. TypeScript return types often expose this defect earlier.

Q: How do you diagnose a Promise that remains pending?

Identify the unresolved operation and every path expected to settle it. Callback wrappers frequently forget error callbacks, early-return without resolve, or wait for an event that already fired. Add bounded diagnostic timestamps around state transitions. Do not merely lengthen the test timeout, because a missing settlement path can wait forever.

Q: Why can parallel tests fail when individual tests pass?

Promises make overlapping execution easy, but the tests may share accounts, files, ports, quotas, or cleanup targets. One test can delete another's fixture or exhaust a connection pool. Generate unique data and isolate mutable resources before increasing worker count. Confirm that helper singletons do not hold per-test state.

Q: How should a Promise failure be reported in CI?

Preserve the original Error stack and attach operation context such as endpoint, test-data identifier, attempt, and elapsed time. Redact tokens and personal data. Do not replace a specific rejection with a generic Test failed message. Correlate browser, API, and application logs using stable request or run IDs.

10. JavaScript Promises Interview Questions QA Automation Coding Round

Q: Write a helper that waits for a condition without fixed sleep assertions.

Poll a side-effect-free asynchronous predicate until it returns truthy or the deadline passes. Delay between attempts to avoid a hot loop, and retain the last useful error as context. The predicate must not create duplicate business actions. In browser frameworks, prefer their built-in expectation polling when it already models the required state.

Q: How would you promisify a Node-style callback?

Wrap one callback invocation in a Promise and reject when the first callback argument is non-null. Resolve with the success value otherwise. Use node:util's promisify for conventional APIs instead of maintaining custom wrappers. Verify that the source callback truly follows the error-first, single-result convention.

Q: How do you collect all failures without losing successful results?

Use Promise.allSettled, then map each outcome together with the corresponding input identifier. Separate fulfilled values from rejection reasons and create a summary. Fail explicitly after the report if any failure violates the test goal. Input ordering makes the identifier association deterministic even when completion order varies.

Q: How would you preserve an original error while adding context?

Throw a new Error with the original error in the standard cause option. Include stable operational context in the new message rather than copying secrets or entire payloads. The stack then points to the contextual boundary while tooling can inspect the cause chain. If no context is added, rethrow the original object rather than throw new Error(error.message).

Q: What code review questions expose Promise bugs?

Ask who owns each started Promise, what the caller awaits, and how rejection reaches the runner. Check loops, event handlers, catch blocks, cleanup, and combinator semantics. Look for shared mutable state under concurrency and operations that continue after timeouts. Confirm that mocks remain asynchronous where production is asynchronous.

How Interviewers Grade Your Answers

Interviewers usually score four dimensions. Accuracy means you distinguish pending, fulfilled, rejected, resolved, tasks, and microtasks. Execution reasoning means you can predict order and trace a rejection through a chain. QA judgment means you connect language behavior to false passes, flaky cleanup, API capacity, and test isolation. Coding quality means the example returns the full operation, preserves errors, and verifies an observable result.

A senior answer also states trade-offs. Promise.all is not always better than a loop, race is not cancellation, and catch is not automatically safe. If you have used a different runner, explain the JavaScript rule first and then translate it to that runner's assertion or fixture API. Never invent a method to sound fluent.

Practice one-minute answers, then extend them when the interviewer adds a constraint. You can upload a resume through the resume analysis dashboard to identify which Promise, JavaScript, API, or Playwright claims are likely to trigger follow-up questions.

Common Mistakes

  • Calling Promises background threads. JavaScript scheduling and host I/O do not make the handler a separate JavaScript thread.
  • Saying resolved always means fulfilled. Resolution can adopt a pending or rejected thenable.
  • Forgetting to return a nested Promise from a chain.
  • Using forEach with an async callback and assuming the loop is awaitable.
  • Swallowing a test failure in catch by logging and returning normally.
  • Believing Promise.all cancels remaining operations after one rejection.
  • Using Promise.race as a timeout without stopping a state-changing loser.
  • Choosing concurrency before checking data isolation and service limits.
  • Awaiting setup but not asynchronous cleanup.
  • Mocking an asynchronous dependency with a synchronous value that hides missing awaits.
  • Asserting only that a Promise settled instead of checking its business result.
  • Adding arbitrary sleeps when an observable condition exists.
  • Replacing an original error and losing its stack or cause.
  • Treating every unhandled rejection warning as if the test runner must fail reliably.

Conclusion

Mastering javascript promises interview questions qa automation roles use requires more than memorizing combinator names. Explain completion ownership, microtask scheduling, value adoption, rejection flow, cleanup, cancellation, and isolation in terms of what makes a test pass or fail.

Run each code block, change its success path into a rejection, and predict the output before executing it. That practice builds the precise mental model interviewers look for and the same discipline that prevents false passes in production automation.

Interview Questions and Answers

What does a JavaScript Promise represent?

It represents one eventual asynchronous outcome. It begins pending and permanently settles as fulfilled with a value or rejected with a reason. A test runner awaits that outcome to track completion and failure.

Why do Promise handlers run after synchronous code?

Handlers are queued as microtasks and cannot interrupt the current call stack. After the stack empties, the runtime drains microtasks before taking the next ordinary task. This is why a resolved Promise handler normally precedes a zero-delay timer.

What happens if you omit return inside a then handler?

The next Promise fulfills with undefined without waiting for the nested operation. The nested rejection may become unhandled, and a test can finish early. Returning the nested Promise connects its result to the chain.

Why is async forEach unsafe in tests?

forEach ignores callback return values and offers no combined Promise to await. Use a for-of loop for sequential work or Promise.all over map for intentional concurrency. The choice must respect shared state and service capacity.

How do Promise.all and allSettled differ?

Promise.all rejects when the first input rejection is observed and fulfills only when all inputs fulfill. allSettled waits for every input and always fulfills with status records. The caller of allSettled must explicitly decide which outcomes fail the test.

How do you prevent catch from swallowing a test failure?

Avoid catching unless you can recover or add useful context. If logging is necessary, rethrow the original error or throw a contextual Error with the original as cause. Returning normally converts the chain to fulfillment.

How would you add a timeout to fetch?

Pass an AbortController signal to fetch and schedule controller.abort at the deadline. Clear the timer in finally, inspect response.ok, and classify an abort separately from an HTTP response error. A race without abort does not stop the request.

How do you test retry logic without slowing the suite?

Inject the asynchronous operation and delay function. Return a deterministic sequence of transient failures followed by success, then assert attempts and result. Also prove that a permanent error is not retried.

Why can Promise concurrency make tests flaky?

Concurrent operations may share mutable accounts, records, files, ports, or resource pools. Isolate data and bound concurrency before parallelizing. Promise APIs coordinate completion but do not create isolation.

What does finally do to a Promise outcome?

It runs after fulfillment or rejection without receiving that outcome. If cleanup succeeds, the original value or reason passes through. If cleanup throws or rejects, its failure replaces the prior outcome.

How do you diagnose a forever-pending Promise?

Trace every path that should call resolve or reject and inspect callback and event boundaries. Add a diagnostic deadline and transition logging. A longer timeout cannot repair a branch that never settles.

What should a strong Promise coding answer demonstrate?

It should return the full asynchronous operation, preserve useful errors, choose correct concurrency semantics, and verify a business result. It should also explain cancellation and cleanup instead of relying on timing luck.

Frequently Asked Questions

What Promise topics are asked in QA automation interviews?

Expect Promise states, chaining, async and await, microtask ordering, rejection handling, combinators, timeouts, cancellation, and test-runner completion. Scenario questions often focus on missing awaits and unsafe concurrency.

Why are JavaScript Promises important for automation testers?

Browser actions, API calls, fixtures, and assertions are frequently asynchronous. Incorrect Promise ownership can create false passes, flaky cleanup, and failures that appear after the runner has finished.

Should I use async and await or then in test code?

Either can be correct, but async and await often make sequential test flow easier to read. Chaining remains useful for transformations, and candidates should understand it because async functions are built on Promises.

What is the most common Promise bug in automated tests?

A missing return or await is especially common. It disconnects work from the Promise observed by the runner, allowing the test to finish before an assertion or rejection occurs.

Is Promise.all safe for parallel tests?

Only when the operations are independent and infrastructure can support the concurrency. Shared users, records, ports, or quotas can make individually stable tests fail when combined.

Does Promise.race cancel slow operations?

No. It settles from the first result, but losing operations continue unless their API supports cancellation and the caller invokes it, such as aborting fetch with AbortController.

How should expected Promise failures be tested?

Use the runner's rejection matcher or Node's assert.rejects. Assert stable properties such as error class, code, and a meaningful message detail, and ensure unexpected fulfillment fails the test.

Related Guides