Я использую Composition API Vue 3 следующим образом:
store.ts
import { ref, Ref } from 'vue';
import { defineStore } from 'pinia';
export const useStore = defineStore('store', () => {
const isLoading: Ref<boolean> = ref(true);
function init() {
isLoading.value = true;
setTimeout(() => (isLoading.value = false), 3000);
}
return {
init,
isLoading,
};
});
Компонент.vue
<script setup lang = "ts">
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useStore } from './store';
const { t } = useI18n();
const store = useStore();
const { isLoading } = storeToRefs(store);
</script>
<template>
<wb-button @click = "store.init()">{{ t('init') }}</wb-button>
<p>Loading: {{ isLoading ? 'Yes' : 'No' }}</p>
</template>
Все это работает отлично и денди, но когда я пытаюсь что-то проверить, все становится грязным. До сих пор я издевался над vue-i18n по всему миру следующим образом:
testSetup.ts
import { vi } from 'vitest';
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => key,
d: (key: string) => key,
}),
}));
Однако это работает только до тех пор, пока я не попытаюсь одновременно издеваться над магазином следующим образом:
Компонент.test.ts
import { it, expect, vi } from 'vitest';
import { mount} from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import Component from './Component.vue';
const options = {
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn(),
initialState: {
items: [],
},
}),
],
},
};
it('works', () => {
const wrapper = mount(Component, options);
expect(wrapper.findComponent(Component)).toBeTruthy();
});
Как только я добавляю изменение в конфигурацию плагина, чтобы включить макет Pinia, макет vue-i18n перестает работать и выдает старый знакомый TypeError: $setup.t is not a function. Я также пытался использовать config.global.mocks и config.global.plugins для имитации vue-i18n, но все эти решения перестают работать, как только я добавляю имитацию Pinia в тесты. Я думаю, причина в том, что объект config.global каким-то образом сбрасывается @pinia/testing, но я не знаю, как предотвратить такое поведение.
пакет.json
"@pinia/testing": "0.0.16",
"@types/jsdom": "21.1.1",
"@types/node": "18.16.2",
"@vue/test-utils": "2.3.2",
"jsdom": "21.1.1"
"typescript": "~4.8.4",
"vite": "4.3.3",
"vitest": "0.30.1",



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Ошибка говорит, что смонтированный компонент не имеет функции t, потому что вы не добавляете VueI18n в качестве плагина к параметрам монтирования:
import { it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import Component from './Component.vue'
import { createI18n } from 'vue-i18n'
const options = {
global: {
plugins: [
createPinia(),
createI18n({
legacy: false,
locale: 'en-US',
messages: {
'en-US': {
init: 'Initialize'
}
}
})
]
}
}
it('does work', () => {
const wrapper = mount(Component, options)
expect(wrapper.findComponent(Component)).toBeTruthy()
})
Более подробно, global.plugins ожидает массив плагинов Vue (объекты или экземпляры классов с функцией install()) и делает то же самое, что и app.use(somePlugin).
Туда нужно поместить все плагины, на которые опирается тестируемый компонент (и потомки, если не мелкое монтирование). Это связано с тем, что каждый компонент тестируется изолированно (поэтому они называются модульными тестами), они не монтируются внутри реального приложения.
Короче говоря, вы видите то же поведение, что и при попытке использовать VueI18n без добавления .use(VueI18n) к app.
Чтобы уменьшить количество шаблонов в тестовых файлах, экспортируйте mountingOptions из test.utils.ts:
import { createPinia } from 'pinia'
import { createI18n } from 'vue-i18n'
export const mountingOptions = {
global: {
plugins: [
createPinia(),
createI18n({
legacy: false,
locale: 'en-US',
messages: {
'en-US': {
init: 'Initialize'
}
}
})
]
}
}
Как правило, mountingOptions должен содержать все плагины, которые вы добавили в app, в main.
Тогда в любом тесте:
import { mountingOptions } from '../test.utils'
//...
const wrapper = mount(Component, mountingOptions)
В этом случае экспорт из «vue-I18n» не является плагином в используемой вами версии. Я предположил, что вы используете последнюю версию. Итог: вы должны предоставить плагин для теста так же, как вы предоставляете его приложению, в main.
В вашем случае: createI18n({ legacy: false, ... }). Эта функция возвращает объект с функцией install(), которая может использоваться app.use() и внутри массива global.plugins вашего теста.
На самом деле вы используете последнюю версию. Это документация, которая кажется устаревшей. Если вы воспользуетесь ссылкой на документы, которые я разместил выше, вы заметите, что задокументированный способ использования плагина заключается в размещении экспорта пакета по умолчанию внутри функции .use(). Поэтому они изменили API в v9.
Я обновил свой ответ для v9. Я надеюсь, что я также сделал понятным принцип, лежащий в основе кода, чтобы вы могли понять, как предоставить другие плагины для ваших тестов, если/когда это необходимо.
Я все еще получаю ту же ошибку (см. обновленное репо). Кроме того, почему я не могу просто издеваться над импортом? Компонент просто использует импортированную t-функцию, верно?
Вы можете издеваться над этим. Но компонент должен использовать его, прежде чем вы сможете над ним издеваться. Если вы хотите, чтобы над ним издевались, вам нужно предоставить плагин для монтирования и макет в тестовой настройке.
хм, хорошо, я в порядке с любым, но в настоящее время оба способа, к сожалению, не работают :/
Хорошо, я посмотрю репозиторий, который вы добавили позже сегодня. В принципе ссылки на репо не рекомендуются, потому что они имеют тенденцию устаревать после ответа на вопрос и внесения исправления в проект. Рекомендуемое решение — предоставить минимальный воспроизводимый пример (codesandbox.io или аналогичный).
Ты прав! Я добавил к вопросу ссылку на песочницу, потому что она слишком длинная для комментария.
Я не смог заставить useTestingPinia() работать, и я не совсем понимаю, почему. Но он отлично работает с правильным хранилищем (например, передайте createPinia() плагинам теста). Вот оно. Если вы хотите разобраться, почему createTestingPinia() не работает, возможно, вам следует спросить в репозитории pinia. Мне никогда не приходилось издеваться над магазином с тех пор, как я начал использовать пинию, я просто экспортирую pinia = createPinia() + все магазины из папки ./store.
Когда я пытаюсь использовать useTestingPinia() в вашем примере, я получаю store.init не является ошибкой функции. По понятным причинам я обновил ответ кодом, который действительно работает.
Проблема в том, что свойство createSpy ожидает функцию, но был предоставлен вызов. Измените vi.fn() на vi.fn, и все заработает, как и ожидалось.
createTestingPinia({
createSpy: vi.fn, <--
...
})
Источник: https://pinia.vuejs.org/cookbook/testing.html#specifying-the-createspy-function
Хорошая находка. Вы предлагаете createI18n() плагин, предоставленный для монтирования, не нужен? На самом деле это не так, потому что вы издеваетесь над всем модулем. Прохладный.
Получение той же ошибки:
[Vue warn]: A plugin must either be a function or an object with an "install" function. TypeError: $setup.t is not a function