【问题标题】:Cypress stub out loadStripeCypress stub out loadStripe
【发布时间】:2021-08-06 11:32:29
【问题描述】:

我在从测试中剔除 Stripe 时遇到了一些麻烦

CartCheckoutButton.ts

import React from 'react'
import { loadStripe } from '@stripe/stripe-js'

import useCart from '~/state/CartContext'
import styles from './CartCheckoutButton.module.scss'

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY)

const CartCheckoutButton = ({}: TCartCheckoutButtonProps) => {
  const { cartItems } = useCart()

  const handleCheckOutOnClick = async (event) => {
    const { sessionId } = await fetch('/api/checkout/session', {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
      },
      body: JSON.stringify({ cartItems }),
    }).then((res) => res.json())

    const stripe = await stripePromise
    const { error } = await stripe.redirectToCheckout({
      sessionId,
    })

    if (error) {
      // TODO: Show some error message
      console.log(error)
    }
  }

  return (
    <div className={styles.container}>
      <button onClick={handleCheckOutOnClick} disabled={cartItems.length == 0}>
        CHECKOUT
      </button>
    </div>
  )
}

export default CartCheckoutButton

EndUserExperience.spec.js

import * as stripeJS from '@stripe/stripe-js'

describe('End user experience', () => {
  beforeEach(() => {
    cy.visit('http://localhost:3000/')

    cy.stub(stripeJS, 'loadStripe').resolves(
      new Promise(function (resolve, reject) {
        resolve({
          redirectToCheckout({ sessionId }) {
            console.log(`redirectToCheckout called with sessionId: ${sessionId}`)
            return new Promise(function (resolve, reject) {
              resolve({ error: true })
            })
          },
        })
      })
    )
  })

  it('Orders some dishes and makes a checkout', () => {
    console.log('working on it')
  })
})

当我点击它时,它仍然会重定向我。所以存根似乎没有启动..

更新二

尝试@RichardMatsen 建议的以下解决方案

import React from 'react'
import * as stripeModule from '@stripe/stripe-js'

import useCart from '~/state/CartContext'
import styles from './CartCheckoutButton.module.scss'

const stripePublishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY

const CartCheckoutButton = ({}: TCartCheckoutButtonProps) => {
  const { cartItems } = useCart()

  // https://stackoverflow.com/questions/67565714/cypress-stub-out-loadstripe
  const stripePromise = React.useCallback(() => {
    window['stripeModule'] = stripeModule
    return stripeModule.loadStripe(stripePublishableKey)
  }, [stripeModule, stripePublishableKey])

  const handleCheckOutOnClick = async (event) => {
    const { sessionId } = await fetch('/api/checkout/session', {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
      },
      body: JSON.stringify({ cartItems }),
    }).then((res) => res.json())

    const stripe = await stripePromise()
    const { error } = await stripe.redirectToCheckout({
      sessionId,
    })

    if (error) {
      // TODO: Show some error message
      console.log(error)
      throw error
    }
  }

  return (
    <div className={styles.container}>
      <button onClick={handleCheckOutOnClick} disabled={cartItems.length == 0}>
        TILL KASSAN
      </button>
    </div>
  )
}

export default CartCheckoutButton

test.spec.js

describe('End user experience', async () => {
  beforeEach(() => {
    cy.visit('http://localhost:3000/')

    cy.window().then((win) => {
      console.log(win)
      cy.stub(win.stripeModule, 'loadStripe').resolves(
        new Promise(function (resolve, reject) {
          resolve({
            redirectToCheckout({ sessionId }) {
              console.log(`redirectToCheckout called with sessionId: ${sessionId}`)
              return new Promise(function (resolve, reject) {
                resolve({ error: true })
              })
            },
          })
        })
      )
    })

    cy.intercept('GET', /.*stripe.*/, (req) => {
      req.redirect('http://localhost:3000/checkout/success')
    })
  })

  it('Orders some dishes and makes a checkout', () => {
    console.log('working on it')
  })
})

但它仍然重定向我并显示错误

Trying to stub property 'loadStripe' of undefined

【问题讨论】:

  • 重新更新:你不能等待cy.window(),它有一个.then() 方法但它不是一个promise。
  • useEffect 不需要设置 stripeModule 引用,但它应该用于推迟对 stripeModule.loadStripe 的调用 - 见下文
  • “尝试存根属性 'loadStripe' of undefined”是因为您设置 window.stripeModule 太晚了。应该在导入后立即完成。

标签: javascript typescript stripe-payments cypress


【解决方案1】:

据我所知,您不能通过在测试中导入它的模块来存根应用程序中的方法,看起来您获得了不同的“实例”。

请参阅这个最近的问题How to Stub a module in Cypress,一种有效的方法是通过window 传递要存根的实例。

CartCheckoutButton.ts

import React, { useCallback } from 'react'
import * as stripeModule from '@stripe/stripe-js';

if (process.browser) {                  // this check needed for NextJS SSR
  if (window.Cypress) {
    window.stripeModule = stripeModule;
  }
}

// const stripePromise = loadStripe(...)  // need to defer this call
                                          // from load time to runtime
                                          // see useCallback below

// Maybe put this outside the React function, 
// since dependencies shouldn't have property references
const stripeKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;



const CartCheckoutButton = ({}: TCartCheckoutButtonProps) => {

   const stripePromise = useCallback(() => {
     return stripeModule.loadStripe(stripeKey);
   }, [stripeModule, stripeKey]);

EndUserExperience.spec.js

beforeEach(() => {

  cy.visit('http://localhost:3000/')
    .then(win => {                      // wait for app to initialize

      const stripeModule = win.stripeModule;
      cy.stub(stripeModule, 'loadStripe').resolves(...

    })

可重现的示例

构建默认的 Next 应用

npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn-starter/tree/master/learn-starter"

将 stripeModule 引用和 useCallback() 添加到 /pages/index.js

import React, { useCallback } from 'react'
import * as stripeModule from '@stripe/stripe-js';

import Head from 'next/head'

if (process.browser) {
  if (window.Cypress) {
    window.stripeModule = stripeModule; 
  }
}

const stripeKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;

export default function Home() {

  const stripePromise = useCallback(() => {
    return stripeModule.loadStripe(stripeKey);
  }, [stripeModule, stripeKey]);

  return (
    <div className="container">
    ...

添加基本测试

it('stubs loadStripe', () => {

  cy.visit('http://localhost:3000/').then(win => {
    const stripeModule = win.stripeModule;
    cy.stub(stripeModule, 'loadStripe').resolves(
      console.log('Hello from stubbed loadStripe')
    )
  })
})

构建、启动、测试

yarn build
yarn start
yarn cypress open

来自cy.stub() 的消息被打印到控制台。

【讨论】:

  • 我没有捕捉到正确的实例是有道理的。但是您的建议似乎不起作用(请参阅更新的问题)
  • 非常感谢您抽出宝贵时间帮助我。您的建议是有道理的,但我无法让它发挥作用(进行第二次更新)。我什至尝试使用next/dynamic 的 no ssr 选项
  • 在钩子内设置stripeModule 引用很可能是在测试尝试使用它之后进行的(因此undefined)。将它设置在 React 组件之外应该会得到更好的结果。
  • 当我这样做时,它会抛出 cy.visit() failed trying to load: http://localhost:3000/
  • 对于 NextJS 使用 if (process.browser) { 来避免 SSR 问题。
猜你喜欢
  • 1970-01-01
  • 2013-07-27
  • 2015-08-21
  • 2019-02-02
  • 2019-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多