Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Yes. Put cy.stub() in a normal JavaScript for loop, forEach(), or map(). It runs synchronously and returns a Sinon stub immediately, not a queued Cypress command. Save the returned stubs (or give them aliases), trigger the application behavior, and then assert on those references.

The key rule: cy.stub() is synchronous

Cypress commands normally enter Cypress’s command queue and are yielded later. cy.stub() is different: Cypress documents it as a utility function, not a command, query, or assertion. The call executes immediately and returns a Sinon stub. That makes an ordinary JavaScript loop the right tool for creating several stubs during test setup.

const methods = ['save', 'remove', 'refresh']
const stubs = methods.map((name) => cy.stub(api, name).as(name))

// Run the code that should call api.save(), api.remove(), or api.refresh().
expect(stubs[0]).to.have.been.calledOnce
cy.get('@refresh').should('have.been.called')

The loop above is finished before Cypress moves on to later queued commands. Keep stubs if you want direct Sinon assertions, or use the aliases when a Cypress assertion reads more clearly.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Stub several methods on one object

Use map() when you need the returned stubs

map() is convenient when the method names are data. Its result is an array in the same order as the input, so a failure can be tied back to a specific method.

it('stubs every persistence method used by the workflow', () => {
  const methodNames = ['save', 'remove', 'refresh']
  const stubs = methodNames.map((name) =>
    cy.stub(api, name).as(name),
  )

  cy.get('[data-cy=run-sync]').click()

  expect(stubs[0]).to.have.been.calledOnce
  expect(stubs[1]).to.have.been.calledWith('temporary-id')
  cy.get('@refresh').should('have.been.called')
})

This pattern replaces each listed method. If one name is not a function on api, creating that stub fails immediately, which is useful feedback that the object shape or the test’s list is wrong.

Use forEach() when you do not need an array

const methods = ['save', 'remove']
const stubs = {}

methods.forEach((name) => {
  stubs[name] = cy.stub(api, name).as(name)
})

cy.get('[data-cy=submit]').click()
expect(stubs.save).to.have.been.called
cy.get('@remove').should('not.have.been.called')

An object keyed by method name is often easier to read than numeric indexes when the test checks many methods.

Use a conventional for loop for conditional setup

const methods = ['save', 'remove', 'refresh']
const stubs = {}

for (let i = 0; i < methods.length; i += 1) {
  const name = methods[i]
  if (Cypress.env('skipRefresh') && name === 'refresh') continue
  stubs[name] = cy.stub(api, name).as(name)
}

cy.get('[data-cy=run]').click()
expect(stubs.save).to.have.been.called

Choose the loop whose control flow best explains the test. There is no Cypress-specific requirement to wrap a stub in cy.then(), return it from a command, or await it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set return values and argument-specific behavior in the loop

Sinon methods can be chained while each stub is created. Cypress’s stubs-and-spies guidance documents returns(), withArgs(), resolves(), and rejects() for controlling behavior.

Give every key its own return value

const keys = ['profile', 'settings', 'flags']
const lookups = keys.map((key) =>
  cy.stub(cache, 'get')
    .withArgs(key)
    .returns(`value:${key}`)
    .as(`cache-${key}`),
)

cy.get('[data-cy=load]').click()
cy.get('@cache-profile').should('have.been.calledWith', 'profile')
expect(lookups[2]).to.have.been.called

Each call to cy.stub(cache, 'get') replaces the same method, so use this pattern only when the test intentionally creates the desired behavior for that method. If you need several argument rules on one method, create one stub and configure it with multiple withArgs() clauses instead of repeatedly replacing the method.

Model resolved and rejected promises

const requests = ['primary', 'backup']
const stubs = requests.map((name) => {
  const stub = cy.stub(client, 'fetch')
  return name === 'primary'
    ? stub.withArgs(name).resolves({ ok: true }).as(`fetch-${name}`)
    : stub.withArgs(name).rejects(new Error('offline')).as(`fetch-${name}`)
})

Configure asynchronous outcomes before the code under test runs. Assertions still happen after the application has had an opportunity to call the stub.

Assertions after the application runs

Creating a stub only replaces a function; it does not exercise the application. Trigger the behavior after setup, then inspect calls with Sinon-Chai assertions or Cypress alias assertions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • expect(stub).to.have.been.called checks that it was called at least once.
  • expect(stub).to.have.been.calledOnce checks the exact count of one.
  • expect(stub).to.have.been.calledWith(value) checks that a call included the argument.
  • cy.get('@alias').should('have.been.called') retrieves an aliased stub through Cypress’s command interface.
const notifyStubs = objects.map((object, index) =>
  cy.stub(object, 'notify').as(`notify${index}`),
)

cy.get('[data-cy=ready]').click()
expect(notifyStubs[1]).to.have.been.calledWith('ready')
cy.get('@notify0').should('have.been.calledOnce')

Keep the input list stable and explicit. When a test fails, a named alias such as @notify1 identifies the target more clearly than an anonymous stub created inside a dense expression.

End-to-end tests: stub window methods before page code loads

For an E2E test, application code may call or capture a browser method during startup. Install those stubs in cy.visit()‘s onBeforeLoad callback, which runs after navigation begins but before the application’s page code loads.

it('handles prompt and alert during startup', () => {
  const methods = ['prompt', 'alert']
  const stubs = {}

  cy.visit('/', {
    onBeforeLoad(win) {
      methods.forEach((method) => {
        stubs[method] = cy.stub(win, method).as(method)
      })
    },
  })

  cy.get('[data-cy=continue]').click()
  cy.get('@prompt').should('have.been.called')
  expect(stubs.alert).to.have.been.called
})

Putting the same loop after cy.visit() is too late if startup code has already called the real function. The callback also gives the stub the correct window object for that page load.

Component tests: install stubs before mounting

Component tests have a simpler timing rule: create the stubs before cy.mount(). Mounting can synchronously execute component setup, so a stub installed afterward cannot affect that earlier work.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
it('uses every injected service', () => {
  const serviceMethods = ['save', 'remove']
  const stubs = serviceMethods.map((name) =>
    cy.stub(service, name).as(name),
  )

  cy.mount(<Editor service={service} />)
  cy.get('[data-cy=save]').click()

  expect(stubs[0]).to.have.been.called
  cy.get('@remove').should('not.have.been.called')
})

If the component receives a newly created object as a prop, stub that exact object before mounting; stubbing a different object will not intercept the component’s calls.

When a loop is the wrong abstraction

Different methods need materially different setup

A loop is clearest when every target follows the same policy. If each method has a different return value, assertion, or explanation, explicit setup can make the test easier to review.

const saveStub = cy.stub(api, 'save').returns({ id: 'new' }).as('save')
const removeStub = cy.stub(api, 'remove').rejects(new Error('locked')).as('remove')

cy.get('[data-cy=run]').click()
expect(saveStub).to.have.been.calledWith('draft')
cy.get('@remove').should('have.been.calledOnce')

The target is not a function

cy.stub(object, methodName) expects the named property to be stub-able. Verify spelling, object construction, and import timing before iterating. A missing target should be fixed in the test setup or production object, not hidden with a conditional that silently skips the stub.

You need observation, not replacement

A stub replaces the original function. Use cy.spy() when the real implementation must continue to run while the test records calls. For example, spying on a formatter preserves its output; stubbing it requires you to provide that output yourself.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

You are controlling HTTP traffic

cy.stub() changes an in-memory function. It does not intercept browser network requests. Use cy.intercept() when the test needs to control an HTTP request or response. A loop can still configure multiple intercepts, but that is a request-layer concern rather than a function stub.

Why cy.stub() inside cy.each() often fails

.each() is a Cypress command that iterates over yielded subjects. A stub is a synchronous value, not a command to be queued and yielded. Treating the two models as interchangeable can produce confusing ordering or subject errors.

For a known list of methods or objects, build the list in ordinary JavaScript before the Cypress chain, as in map(), forEach(), or for examples above. If the list itself must come from the browser, first obtain it with a Cypress command, then use a cy.then() callback to perform synchronous setup on the yielded value:

cy.get('[data-method]').then(($items) => {
  const names = [...$items].map((item) => item.getAttribute('data-method'))
  names.forEach((name) => {
    cy.stub(api, name).as(name)
  })
})

cy.get('[data-cy=run]').click()

The callback is used here to access a value yielded by Cypress; the individual cy.stub() calls remain synchronous.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Lifecycle, isolation, and cleanup

Cypress creates stubs in a sandbox and automatically resets and restores them between tests. You normally do not need an afterEach() hook that manually restores every stub. Keeping setup inside each test also prevents one test’s assumptions from leaking into another.

E2E test isolation resets the browser context between tests according to the project’s Cypress configuration. Do not rely on arbitrary shared JavaScript state, aliases, or application data surviving into the next test. Recreate the loop and its target objects in each test or in a deliberately scoped beforeEach().

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting loop-created stubs

Symptom Likely cause Fix
The value from cy.stub() is undefined or cannot be awaited The test treats the stub like a chainable Cypress command. Use the returned value synchronously; do not await it or return it as a Cypress chain.
The real function runs The stub was installed after the function was called, or the code uses a different object reference. Stub before the action; for E2E window methods use onBeforeLoad, and for component tests stub before cy.mount().
A method name fails during setup The property is misspelled, absent, or not a function on the target object. Log or inspect the object shape, use the exact method name, and keep the method list explicit.
An alias assertion cannot find the stub The alias was not assigned, or the alias name differs from the assertion. Call .as(name) while creating the stub and use the exact @name.
Call-count assertions are flaky The test asserts before the application action or before an asynchronous operation completes. Trigger the behavior first and use Cypress’s retryable assertions around the alias or a synchronization point in the UI.
The original behavior is needed but is missing A stub replaced the implementation. Use cy.spy(), or configure the stub with the return or promise behavior the test requires.
An HTTP response is unchanged The test stubbed a client method rather than the browser request. Use cy.intercept() for request and response control.

Choosing the right test double

Need Use Effect
Replace an in-memory function and control its result cy.stub() The original method is replaced.
Record calls while preserving original behavior cy.spy() The original method still runs.
Control an HTTP response cy.intercept() The browser request or response is stubbed at the network layer.

Use a loop when the targets are data-driven and share a setup policy. Prefer explicit statements when the setup itself communicates important business behavior. In both cases, install the test double before the code that must observe it and retain a reference or alias for the assertion.

Performance and reliability considerations

  • Creating a modest list of synchronous stubs is setup work in the test runner; it does not add browser network traffic.
  • Large, generated lists can make failures harder to diagnose. Keep the input list stable, name aliases predictably, and split unrelated behaviors into separate tests.
  • Do not use a stub as a shortcut for waiting. Synchronize on a visible result, a relevant alias assertion, or the application event that proves the call should have happened.
  • Restore behavior is normally handled by Cypress’s sandbox between tests, so manual cleanup can introduce unnecessary complexity.

Or skip the browser setup

If the goal is to capture a page image or PDF rather than exercise browser code, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL (see the ScreenshotNeo API docs):

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And in Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Its Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account to try it without a card.

Frequently Asked Questions

Can I create aliases from a loop?

Yes. Call .as(name) on each returned stub, then use the matching @name in a later Cypress assertion.

Should I use map or forEach?

Use map() when you want an array of stub references; use forEach() when aliases or a keyed object are sufficient.

What happens to loop-created stubs after a test?

Cypress’s sandbox automatically resets and restores stubs between tests, so manual restoration is usually unnecessary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Bottom Line

Use a normal JavaScript loop to create cy.stub() instances, install them before the code under test runs, and assert through saved references or aliases afterward.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.