如何使用 vue-test-utils 和 Jest 在 Nuxt 中对使用 vuex-module-decorators 语法定义的 Vuex 模块进行单元测试?
Posted
技术标签:
【中文标题】如何使用 vue-test-utils 和 Jest 在 Nuxt 中对使用 vuex-module-decorators 语法定义的 Vuex 模块进行单元测试?【英文标题】:How to unit test Vuex modules defined with vuex-module-decorators syntax in Nuxt, using vue-test-utils and Jest? 【发布时间】:2021-11-17 17:50:54 【问题描述】:我在任何地方都找不到这个问题的答案。
我浏览了 Nuxt 官方文档以及现有的 Stack Overflow 和 Github 问题讨论。
AuthModule 的实现:
@Module(
stateFactory: true,
namespaced: true,
)
export default class AuthModule extends VuexModule
userData?: UserData | undefined = undefined;
prevRouteList: Routes[] = [];
error?: services.ICognitoError | undefined = undefined;
isLoading = false;
...
@VuexMutation
setIsLoading(isLoading: boolean)
this.isLoading = isLoading;
...
@VuexAction( rawError: true )
async register(registerData: email: string; password: string ): Promise<any>
this.context.commit('setIsLoading', true);
this.context.commit('setError', undefined);
this.context.commit('setInitiateRegistration', false);
this.context.dispatch('setEmail', registerData.email);
try
const user = await services.register(registerData.email, registerData.password);
if (user)
this.context.dispatch('pushPrevRoute', Routes.emailVerification);
this.context.commit('setInitiateRegistration', true);
catch (error: any)
this.context.commit('setError', error);
this.context.commit('setInitiateRegistration', false);
this.context.commit('setIsLoading', false);
...
@MutationAction
setEmail(email: string) ...
...
get getEmail()
return this.email;
...
我的/store
目录仅包含 Vuex 模块(如示例 AuthModule)。我在其中声明和实例化存储的地方没有 index.ts。而且这些模块不是动态的。
所以我的问题是:
为 Nuxt Vuex 模块编写单元测试的正确模式是什么,使用 vuex-module-decorators synax 定义,使用 Jest 和 vue-test-utils?
如何对 VuexMutations、VuexActions、MutationActions、getter 等进行单元测试?
我尝试在测试文件中实例化 AuthModule 类,但我无法让它工作。
describe('AuthModule', () =>
const authModule = new AuthModule(...);
it('test', () =>
console.log(authModule);
/*
AuthModule
actions: undefined,
mutations: undefined,
state: undefined,
getters: undefined,
namespaced: undefined,
modules: undefined,
userData: undefined,
prevRouteList: [],
error: undefined,
isLoading: false,
registrationInitiated: false,
registrationConfirmed: false,
forgotPasswordEmailSent: false,
forgottenPasswordReset: false,
email: '',
maskedEmail: ''
*/
);
我也尝试了这里解释的方法:
https://medium.com/@brandonaaskov/how-to-test-nuxt-stores-with-jest-9a5d55d54b28
这里:
Testing a NUXT.js and Vue.js app with Jest. Getting '[vuex] module namespace not found in mapState()' and '[vuex] unknown action type'
这是我根据这些文章/链接中的建议进行的设置:
// jest.config.js
module.exports =
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
roots: [
'<rootDir>/components',
'<rootDir>/pages',
'<rootDir>/middleware',
'<rootDir>/layouts',
'<rootDir>/services',
'<rootDir>/store',
'<rootDir>/utils',
],
reporters: ['default', 'jest-sonar'],
moduleNameMapper:
'^@/(.*)$': '<rootDir>/$1',
'^~/(.*)$': '<rootDir>/$1',
'^vue$': 'vue/dist/vue.common.js',
,
moduleFileExtensions: ['ts', 'js', 'vue', 'json'],
testEnvironment: 'jsdom',
transform:
'^.+\\.ts$': 'ts-jest',
'.*\\.(vue)$': 'vue-jest',
'^.+\\.(js|jsx)$': 'babel-jest-amcharts',
,
collectCoverage: true,
collectCoverageFrom: [
'<rootDir>/components/**/*.vue',
'<rootDir>/pages/**/*.vue',
'<rootDir>/layouts/**/*.vue',
'<rootDir>/middleware/**/*.ts',
'<rootDir>/store/**/*.ts',
'<rootDir>/mixins/**/*.ts',
'<rootDir>/services/**/*.ts',
],
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\](?!(@amcharts)\\/).+\\.(js|jsx|ts|tsx)$'],
forceExit: !!process.env.CI,
;
// jest.setup.js
import config from '@vue/test-utils';
import Nuxt, Builder from 'nuxt';
import TsBuilder from '@nuxt/typescript-build';
import nuxtConfig from './nuxt.config';
config.stubs.nuxt = template: '<div />' ;
config.stubs['nuxt-link'] = template: '<a><slot></slot></a>' ;
config.mocks.$t = (msg) => msg;
const nuxtResetConfig =
loading: false,
loadingIndicator: false,
fetch:
client: false,
server: false,
,
features:
store: true,
layouts: false,
meta: false,
middleware: false,
transitions: false,
deprecations: false,
validate: false,
asyncData: false,
fetch: false,
clientOnline: false,
clientPrefetch: false,
clientUseUrl: false,
componentAliases: false,
componentClientOnly: false,
,
build:
indicator: false,
terser: false,
,
;
const nuxtBuildConfig =
...nuxtConfig,
...nuxtResetConfig,
dev: false,
extensions: ['ts'],
s-s-r: false,
srcDir: nuxtConfig.srcDir,
ignore: ['**/components/**/*', '**/layouts/**/*', '**/pages/**/*'],
;
const buildNuxt = async () =>
const nuxt = new Nuxt(nuxtBuildConfig);
await nuxt.moduleContainer.addModule(TsBuilder);
try
await new Builder(nuxt).build();
return nuxt;
catch (error)
console.log(error);
process.exit(1);
;
module.exports = async () =>
const nuxt = await buildNuxt();
process.env.buildDir = nuxt.options.buildDir;
;
// jest.utils.js
import Vuex from 'vuex';
import VueRouter from 'vue-router';
import VueFormulate from '@braid/vue-formulate';
import mount, createLocalVue from '@vue/test-utils';
const createStore = (storeOptions = ) => new Vuex.Store( ...storeOptions );
const createRouter = () => new VueRouter();
const setup = (storeOptions) =>
const localVue = createLocalVue();
localVue.use(VueRouter);
localVue.use(Vuex);
localVue.use(VueFormulate);
const store = createStore(storeOptions);
const router = createRouter();
return store, router, localVue ;
;
export const createNuxtStore = async () =>
const storePath = `$process.env.buildDir/store.js`;
// console.log(storePath);
const NuxtStoreFactory = await import(storePath);
const nuxtStore = await NuxtStoreFactory.createStore();
return nuxtStore ;
;
export const createTestBed =
(component, componentOptions = , storeOptions = ) =>
(renderer = mount) =>
const localVue, store, router = setup(storeOptions);
return renderer(component,
store,
router,
localVue,
...componentOptions,
);
;
// auth.spec.js
import createNuxtStore from '@/jest.utils';
describe('AuthModule', () =>
let store: any;
beforeAll(() =>
store = createNuxtStore();
);
it('should create', () =>
console.log(store);
);
);
运行此程序后,我在控制台中收到此错误:
RUNS store/auth.spec.ts
node:internal/process/promises:245
triggerUncaughtException(err, true /* fromPromise */);
^
ModuleNotFoundError: Cannot find module 'undefined/store.js' from 'jest.utils.js'
at Resolver.resolveModule (/Users/ivan.spoljaric/Documents/.../node_modules/jest-resolve/build/index.js:306:11)
at Resolver._getVirtualMockPath (/Users/ivan.spoljaric/Documents/.../node_modules/jest-resolve/build/index.js:445:14)
at Resolver._getAbsolutePath (/Users/ivan.spoljaric/Documents/.../node_modules/jest-resolve/build/index.js:431:14)
at Resolver.getModuleID (/Users/ivan.spoljaric/Documents/.../node_modules/jest-resolve/build/index.js:404:31)
at Runtime._shouldMock (/Users/ivan.spoljaric/Documents/.../node_modules/jest-runtime/build/index.js:1521:37)
at Runtime.requireModuleOrMock (/Users/ivan.spoljaric/Documents/.../node_modules/jest-runtime/build/index.js:916:16)
at /Users/ivan.spoljaric/Documents/.../jest.utils.js:24:28
at processTicksAndRejections (node:internal/process/task_queues:94:5)
at Object.createNuxtStore (/Users/ivan.spoljaric/Documents/.../jest.utils.js:24:28)
code: 'MODULE_NOT_FOUND',
hint: '',
requireStack: undefined,
siblingWithSimilarExtensionFound: false,
moduleName: 'undefined/store.js',
_originalMessage: "Cannot find module 'undefined/store.js' from 'jest.utils.js'"
【问题讨论】:
【参考方案1】:经过反复试验,我终于找到了问题的答案。
如果你和我一样;仅从 Vue、Nuxt 和 vuex-module-decorators 开始您的旅程,并且您在解决完全相同的问题时遇到了困难,我希望这个小小的单人 QA 乒乓球能找到您!
我的解决方案如下所示:
// auth.spec.ts
import Vuex, Store from 'vuex';
import createLocalVue from '@vue/test-utils';
import AuthModule, IState from './auth';
jest.mock('@/services');
const localVue = createLocalVue();
localVue.use(Vuex);
const storeOptions =
modules:
auth: AuthModule,
,
;
const createStore = (storeOptions: any = ): Store< auth: IState > => new Vuex.Store( ...storeOptions );
describe('AuthModule', () =>
let store: Store< auth: IState >;
beforeEach(() =>
store = createStore(storeOptions);
);
describe('mutations', () =>
// ...
it('auth/setIsLoading', () =>
expect(store.state.auth.isLoading).toBe(false);
store.commit('auth/setIsLoading', true);
expect(store.state.auth.isLoading).toBe(true);
);
// ...
);
describe('actions', () =>
// ...
it('register success', async () =>
const registerData =
email: 'dummy@email.com',
password: 'dummy',
;
expect(store.state.auth.registrationInitiated).toBe(false);
try
await store.dispatch('auth/register', registerData);
expect(store.state.auth.registrationInitiated).toBe(true);
catch (error)
);
// ...
);
describe('mutation-actions', () =>
// ...
it('setEmail', async () =>
const dummyEmail = 'dummy@email.com';
expect(store.state.auth.email).toBe('');
await store.dispatch('auth/setEmail', dummyEmail);
expect(store.state.auth.email).toBe(dummyEmail);
);
// ...
);
describe('getters', () =>
// ...
it('auth/getError', () =>
expect(store.state.auth.error).toBe(undefined);
expect(store.getters['auth/getError']).toBe(undefined);
(store.state.auth.error as any) = 'Demmo error';
expect(store.getters['auth/getError']).toBe('Demmo error');
);
// ...
);
);
// services/auth
export async function register(email: string, password: string, attr: any = ): Promise<any>
try
return await Auth.signUp(
username: email,
password,
attributes:
...attr,
,
);
catch (err: any)
return Promise.reject(createError(err, 'register'));
// createError is just a util method for formatting the error message and wiring to the correct i18n label
// services/__mock__/auth
import createError from '../auth';
export const register = (registerData: email: string; password: string ) =>
try
if (!registerData)
throw new Error('dummy error');
return new Promise((resolve) => resolve( response: user: registerData.email ));
catch (err)
return Promise.reject(createError(err, 'register'));
;
//
要意识到的最重要的一点是,vuex-module-decorators 基于类的模块的行为就像引擎盖下的 vue-class-component。
所有 vuex-module-decorators 的东西都只是语法糖 - vue-class-component API 的包装器。
引用the docs:
在您的商店中,您将 MyModule 类本身用作模块...方式 我们使用的 MyModule 类不同于经典的面向对象 编程,类似于 vue-class-component 的工作方式。我们使用 类本身作为模块,而不是类构造的对象
要记住的另一件事是使用createLocalVue,它使我们能够在不污染全局Vue 类的情况下使用Vue 类、插件、组件等。
将 Vuex 插件添加到 createLocalVue
:
localVue.use(Vuex);
AuthModule 类在 Vuex.Store 构造函数中声明为 Vuex(命名空间)模块(根据文档)。
const storeOptions =
modules:
auth: AuthModule,
,
;
const createStore = (storeOptions: any = ): Store< auth: IState > => new Vuex.Store( ...storeOptions );
在上面的实现中,在 beforeEach
钩子的帮助下,为每个测试用例重新创建了 AuthModule(包括存储、操作、突变、getter...)(因此我们为每个测试都有一个干净的存储)
剩下的就很简单了。你可以看到我是如何测试 AuthModule 的每个部分(动作、突变、getter..)
【讨论】:
以上是关于如何使用 vue-test-utils 和 Jest 在 Nuxt 中对使用 vuex-module-decorators 语法定义的 Vuex 模块进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 vue-test-utils 打开 bootstrap-vue 模态?
如何在单元测试期间使用带有 shallowMount 的 vue-test-utils 找到元素组件?
VueJs 如何删除全局错误处理程序以使用 vue-test-utils 进行测试
vue-test-utils:如何在 mount() 生命周期钩子(使用 vuex)中测试逻辑?
如何使用 vue-test-utils 和 Jest 在 Nuxt 中对使用 vuex-module-decorators 语法定义的 Vuex 模块进行单元测试?