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

如何正确测试Vuex?请求协助测试Vuex Store及关联按钮方法

测试Vuex Store关联的按钮交互

嘿,第一次搞Vuex测试确实会有点懵,我来帮你拆解成简单的步骤,咱们一步步验证这两个按钮的行为是否符合预期!

准备工作

首先确保你已经配置好测试环境:一般用@vue/test-utils(Vue测试工具库)搭配Jest(测试运行器)来写测试用例。我们需要模拟Vuex的store、$router还有socketUrl这些依赖,这样不用真正调用后端接口就能测试逻辑。


测试「登录」按钮(调用handleLogin)

这个方法的核心是调用this.$store.dispatch('login'),然后根据返回结果跳转路由。我们要验证:

  • 点击按钮时,dispatch('login')被正确调用,参数是表单数据
  • 当login dispatch返回包含id_user的响应时,会触发路由跳转

测试代码示例

import { mount } from '@vue/test-utils'
import YourLoginComponent from '@/components/YourLoginComponent.vue'

describe('Login Component - Login Button', () => {
  let wrapper
  let mockStore
  let mockRouter

  beforeEach(() => {
    // 模拟Vuex Store:mock dispatch方法,让它返回我们控制的Promise
    mockStore = {
      dispatch: jest.fn()
    }

    // 模拟Vue Router:mock push方法
    mockRouter = {
      push: jest.fn()
    }

    // 挂载组件,注入模拟的store和router
    wrapper = mount(YourLoginComponent, {
      global: {
        mocks: {
          $store: mockStore,
          $router: mockRouter
        },
        // 让登录按钮显示出来
        data() {
          return {
            loginBtn: true,
            loginForm: { username: 'testuser', password: 'testpass' }
          }
        }
      }
    })
  })

  it('点击登录按钮时,调用store.dispatch("login")并传入表单数据', async () => {
    // 模拟事件对象(因为方法里调用了e.preventDefault())
    const mockEvent = { preventDefault: jest.fn() }
    
    // 找到登录按钮并触发点击事件
    const loginBtn = wrapper.find('.main-form__login-submit')
    await loginBtn.trigger('click', mockEvent)

    // 验证preventDefault被调用
    expect(mockEvent.preventDefault).toHaveBeenCalled()
    // 验证dispatch被调用,参数正确
    expect(mockStore.dispatch).toHaveBeenCalledWith('login', { username: 'testuser', password: 'testpass' })
  })

  it('当login返回含id_user的响应时,跳转到首页', async () => {
    // 让dispatch返回一个包含id_user的Promise
    mockStore.dispatch.mockResolvedValue({ id_user: 123 })
    
    const mockEvent = { preventDefault: jest.fn() }
    const loginBtn = wrapper.find('.main-form__login-submit')
    await loginBtn.trigger('click', mockEvent)

    // 验证路由跳转被调用
    expect(mockRouter.push).toHaveBeenCalledWith({ path: '/' })
  })

  it('当login返回不含id_user的响应时,不跳转路由', async () => {
    // 让dispatch返回不含id_user的Promise
    mockStore.dispatch.mockResolvedValue({})
    
    const mockEvent = { preventDefault: jest.fn() }
    const loginBtn = wrapper.find('.main-form__login-submit')
    await loginBtn.trigger('click', mockEvent)

    // 验证路由跳转没被调用
    expect(mockRouter.push).not.toHaveBeenCalled()
  })
})

测试「退出其他窗口」按钮(调用websocketAuth)

这个方法的逻辑更复杂:调用dispatch("logoutEverywhere"),然后触发socket事件,根据回调结果调用duplicateLoginClear。我们要验证:

  • 点击按钮时,dispatch("logoutEverywhere")被正确调用
  • socket的logoutEverywhere事件被触发,参数正确
  • 当socket回调返回true时,调用duplicateLoginClear

测试代码示例

import { mount } from '@vue/test-utils'
import YourLoginComponent from '@/components/YourLoginComponent.vue'

// 模拟socketUrl对象,mock emit方法
const mockSocketUrl = {
  emit: jest.fn((event, data, callback) => {
    // 默认让回调返回true,方便测试
    callback(true)
  })
}

describe('Login Component - Logout Everywhere Button', () => {
  let wrapper
  let mockStore

  beforeEach(() => {
    mockStore = {
      dispatch: jest.fn()
    }

    wrapper = mount(YourLoginComponent, {
      global: {
        mocks: {
          $store: mockStore,
          // 注入模拟的socketUrl
          socketUrl: mockSocketUrl
        },
        // 让退出按钮显示出来
        data() {
          return {
            showLogoutEverywhereBtn: true,
            loginForm: { username: 'testuser', password: 'testpass' }
          }
        }
      }
    })

    // 每次测试前重置mock的调用记录,避免干扰
    jest.clearAllMocks()
  })

  it('点击按钮时,调用store.dispatch("logoutEverywhere")并传入表单数据', async () => {
    const mockEvent = { preventDefault: jest.fn() }
    const logoutBtn = wrapper.find('.main-form__login-session')
    await logoutBtn.trigger('click', mockEvent)

    expect(mockEvent.preventDefault).toHaveBeenCalled()
    expect(mockStore.dispatch).toHaveBeenCalledWith(
      'logoutEverywhere',
      { user_login: 'testuser', user_password: 'testpass' }
    )
  })

  it('当logoutEverywhere返回结果后,触发socket的logoutEverywhere事件', async () => {
    // 让dispatch返回一个用户ID
    mockStore.dispatch.mockResolvedValue(123)
    
    const mockEvent = { preventDefault: jest.fn() }
    const logoutBtn = wrapper.find('.main-form__login-session')
    await logoutBtn.trigger('click', mockEvent)

    // 验证socket.emit被调用,参数正确
    expect(mockSocketUrl.emit).toHaveBeenCalledWith(
      'logoutEverywhere',
      { userId: 123, page: 'login' },
      expect.any(Function) // 第三个参数是回调函数,只要验证它存在就行
    )
  })

  it('当socket回调返回true时,调用duplicateLoginClear', async () => {
    mockStore.dispatch.mockResolvedValue(123)
    
    const mockEvent = { preventDefault: jest.fn() }
    const logoutBtn = wrapper.find('.main-form__login-session')
    await logoutBtn.trigger('click', mockEvent)

    // 因为我们在mock里让回调返回true,所以验证duplicateLoginClear被调用
    expect(mockStore.dispatch).toHaveBeenCalledWith('duplicateLoginClear')
  })

  it('当socket回调返回false时,不调用duplicateLoginClear', async () => {
    // 重新mock socket.emit,让回调返回false
    mockSocketUrl.emit.mockImplementation((event, data, callback) => {
      callback(false)
    })
    
    mockStore.dispatch.mockResolvedValue(123)
    
    const mockEvent = { preventDefault: jest.fn() }
    const logoutBtn = wrapper.find('.main-form__login-session')
    await logoutBtn.trigger('click', mockEvent)

    // 验证duplicateLoginClear没被调用
    expect(mockStore.dispatch).not.toHaveBeenCalledWith('duplicateLoginClear')
  })
})

一些关键提示

  • Mock依赖是核心:我们不需要真正运行Vuex的action或者socket连接,只要验证组件是否正确调用了这些依赖即可。
  • async/await不能忘:因为触发事件、Promise的执行都是异步的,必须用await确保测试等待这些操作完成。
  • 重置Mock:在每个测试用例前用jest.clearAllMocks()清空调用记录,避免测试之间互相干扰。

内容的提问来源于stack exchange,提问作者Евлампий Романов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:59:15