Cypress测试React应用中Spotify登录按钮跳转URL失败,求排查解决
Hey there! Let's figure out why your stubs aren't catching the redirect and fix this step by step.
1. 为什么第一个 stub 直接失效?
You tried stubbing window.location.href first, but that approach doesn't work because href isn't a regular function—it's a getter/setter property. Cypress can't directly intercept assignments to this property, so your test never detected the jump attempt.
2. 为什么第二个方法 still 失败?
After you added the global window.redirectToSpotifyAuth function in your app code, your Cypress stub got overwritten. Here's why:
- You created the stub in
onBeforeLoad(before the page loads), but when your app's module loads, it reassignswindow.redirectToSpotifyAuth, wiping out the stub you just set up. - Also,
redirectToAuthorizationUrlis an async function. Cypress doesn't wait for async operations by default, which might mean your assertion runs before the stub is ever called.
让我们修复它:3种可行方案
方案一:Stub window.location.assign(无需修改应用代码,最推荐)
When you set window.location.href = url, browsers actually call window.location.assign(url) under the hood. So we can stub this method instead, and it will catch the redirect without touching your app code at all.
// login.cy.js describe('Login Functionality', () => { describe('When you click Login to Spotify', () => { it('constructs the correct Spotify auth URL', () => { cy.visit('/', { onBeforeLoad(window) { // Stub the assign method that gets called when href is set cy.stub(window.location, 'assign').as('redirect'); } }); cy.get('.login-button').click(); // Use a regex to match the Spotify auth domain/path cy.get('@redirect').should('be.calledWithMatch', /accounts.spotify.com\/authorize/); }); }); });
方案二:避免全局函数被覆盖
If you prefer sticking with the global window.redirectToSpotifyAuth approach, tweak your app code to only define the function if it doesn't already exist. This way, Cypress's stub won't get wiped out:
// spotifyAuth.js export const redirectToAuthorizationUrl = async () => { logMessage(`Redirecting to authorization URL...`); const authUrlToNavigateTo = (await getAuthorizationURL()).authorizationURL; // Only define the global function if it doesn't exist yet if (!window.redirectToSpotifyAuth) { window.redirectToSpotifyAuth = function (authUrl) { window.location.href = authUrl; }; } window.redirectToSpotifyAuth(authUrlToNavigateTo); }
Then update your test to just create the stub directly in onBeforeLoad:
// login.cy.js describe('Spotify Login Flow', () => { it('should construct correct Spotify auth URL', () => { cy.visit('http://localhost:3000/', { onBeforeLoad(window) { cy.stub(window, 'redirectToSpotifyAuth').as('redirect'); }, }); cy.get('.login-button').click(); cy.get('@redirect').should('be.calledWithMatch', /accounts.spotify.com\/authorize/); }); });
方案三:直接Stub应用导出的函数
If you don't mind importing your app's auth module into the test, you can stub the redirectToAuthorizationUrl function itself:
// login.cy.js import * as spotifyAuth from '../path/to/spotifyAuth'; // Adjust the path to match your project describe('Login Functionality', () => { it('constructs the correct Spotify auth URL', () => { // Stub the exported function before visiting the page cy.stub(spotifyAuth, 'redirectToAuthorizationUrl') .callsFake(async () => { // Get the URL without actually redirecting const { authorizationURL } = await spotifyAuth.getAuthorizationURL(); cy.wrap(authorizationURL).as('authUrl'); }) .as('redirect'); cy.visit('/'); cy.get('.login-button').click(); cy.get('@redirect').should('have.been.called'); cy.get('@authUrl').should('match', /accounts.spotify.com\/authorize/); }); });
Extra Tips
- Since all your auth logic is async, you don't need to add extra waits—Cypress's assertions automatically retry until they pass (or time out), so
cy.get('@redirect').should(...)will wait for the async call to finish. - If you want to validate specific URL parameters (like
scopeorclient_id), you can parse the URL from the stub's arguments:cy.get('@redirect').then(stub => { const authUrl = new URL(stub.firstCall.args[0]); expect(authUrl.searchParams.get('scope')).to.equal('user-library-read'); expect(authUrl.searchParams.get('client_id')).to.equal(yourClientId); // Replace with your actual client ID });
备注:内容来源于stack exchange,提问作者Milo Cowell

