You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vitest测试renderSetupPage时,按钮点击后startGame Spy未触发问题

问题分析与解决方案

问题原因

  1. 内部函数引用不匹配:renderSetupPage里的按钮点击事件直接调用的是模块作用域内的startGame函数,而非你通过utils.startGame访问的导出函数。你用vi.spyOn(utils, 'startGame') mock的是导出的函数,不会影响模块内部的直接调用。
  2. Mock时机错误:你在describe块中提前执行了utils.renderSetupPage(container),此时按钮的onclick已经绑定了原始的startGame函数,后续的mock无法修改这个已绑定的引用。

解决方案

方案一:调整源码,让内部调用导出的函数

修改../scripts/utils.js中的按钮点击逻辑,改为调用导出的startGame:

export const renderSetupPage = (container) => {
  // 创建父元素部分不变
  const div = document.createElement("div")
  div.innerHTML = 
  `
  <label for="seed">Seed:</label>
  <input type="text" id="seed" name="seed">
  `

  // 创建按钮部分修改点击事件
  const button = document.createElement("button")
  button.id = "start-game-button"
  button.innerText = "Start Game"
  // 改为调用导出的startGame
  button.onclick = () => exports.startGame()

  div.appendChild(button)
  container.appendChild(div)
}

export const startGame = () => {
  window.alert("game started")
}

然后调整测试代码,确保先mock再渲染页面,且每个测试独立初始化DOM:

import { test, expect, vi, describe } from "vitest";
import * as utils from '../scripts/utils';
import {JSDOM} from "jsdom"

describe.only("that setup page is build correctly", ()=>{
    // 每个测试重新创建DOM环境
    const getContainer = () => {
        const dom = new JSDOM("<div id='container'></div>")
        global.document = dom.window.document
        return document.getElementById("container")
    }

    test("that create button exists",() => {
        const container = getContainer()
        utils.renderSetupPage(container)
        const button = container.getElementsByTagName("button")
        expect(button.length).toBe(1)
        expect(button[0].id).toBe("start-game-button")
    })
 
    test("that button click starts game",()=>{
        // 先mock导出的startGame
        const startGameSpy = vi.spyOn(utils, 'startGame').mockImplementation(()=>{})
        
        const container = getContainer()
        // mock后再渲染页面,确保按钮绑定mock后的函数
        utils.renderSetupPage(container)
        const button = container.getElementsByTagName("button")

        button[0].click()
        expect(startGameSpy).toHaveBeenCalled()
        
        // 恢复原始函数,避免影响其他测试
        startGameSpy.mockRestore()
    })
})

方案二:不修改源码,用模块mock实现测试

如果不想改动业务代码,可以用Vitest的vi.mock对整个模块做partial mock,保留原始的renderSetupPage,替换startGame为mock函数:

import { test, expect, vi, describe } from "vitest";
import {JSDOM} from "jsdom"

// Partial mock:保留原始的renderSetupPage,mock startGame
vi.mock('../scripts/utils', async () => {
  const originalModule = await vi.importActual('../scripts/utils');
  return {
    ...originalModule,
    startGame: vi.fn()
  }
});

import * as utils from '../scripts/utils';

describe.only("that setup page is build correctly", ()=>{
    const getContainer = () => {
        const dom = new JSDOM("<div id='container'></div>")
        global.document = dom.window.document
        return document.getElementById("container")
    }

    test("that create button exists",() => {
        const container = getContainer()
        utils.renderSetupPage(container)
        const button = container.getElementsByTagName("button")
        expect(button.length).toBe(1)
        expect(button[0].id).toBe("start-game-button")
    })
 
    test("that button click starts game",()=>{
        const container = getContainer()
        utils.renderSetupPage(container)
        const button = container.getElementsByTagName("button")

        button[0].click()
        // 断言mock的startGame被调用
        expect(utils.startGame).toHaveBeenCalled()
    })
})

内容的提问来源于stack exchange,提问作者Jan Seipel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 13:36:13