Free tools Windows power users keep installed
One-click scans. No signup required.
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.
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.
#1 Best Overall
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.
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.
Rank #2
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.
expect(stub).to.have.been.calledchecks that it was called at least once.expect(stub).to.have.been.calledOncechecks 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.
Rank #3
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.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →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.
Recommended Free Tools
Rank #4
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.
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().
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.
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.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchThe 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.
Quick Recap
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.

