Как одновременно мокать Pinia и vue-i18n?

Я использую 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",
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
0
175
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ошибка говорит, что смонтированный компонент не имеет функции 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 warn]: A plugin must either be a function or an object with an "install" function. TypeError: $setup.t is not a function

leonheess 05.05.2023 12:34

В этом случае экспорт из «vue-I18n» не является плагином в используемой вами версии. Я предположил, что вы используете последнюю версию. Итог: вы должны предоставить плагин для теста так же, как вы предоставляете его приложению, в main.

tao 05.05.2023 14:13

В вашем случае: createI18n({ legacy: false, ... }). Эта функция возвращает объект с функцией install(), которая может использоваться app.use() и внутри массива global.plugins вашего теста.

tao 05.05.2023 14:16

На самом деле вы используете последнюю версию. Это документация, которая кажется устаревшей. Если вы воспользуетесь ссылкой на документы, которые я разместил выше, вы заметите, что задокументированный способ использования плагина заключается в размещении экспорта пакета по умолчанию внутри функции .use(). Поэтому они изменили API в v9.

tao 05.05.2023 14:26

Я обновил свой ответ для v9. Я надеюсь, что я также сделал понятным принцип, лежащий в основе кода, чтобы вы могли понять, как предоставить другие плагины для ваших тестов, если/когда это необходимо.

tao 05.05.2023 14:27

Я все еще получаю ту же ошибку (см. обновленное репо). Кроме того, почему я не могу просто издеваться над импортом? Компонент просто использует импортированную t-функцию, верно?

leonheess 07.05.2023 18:35

Вы можете издеваться над этим. Но компонент должен использовать его, прежде чем вы сможете над ним издеваться. Если вы хотите, чтобы над ним издевались, вам нужно предоставить плагин для монтирования и макет в тестовой настройке.

tao 08.05.2023 09:26

хм, хорошо, я в порядке с любым, но в настоящее время оба способа, к сожалению, не работают :/

leonheess 08.05.2023 10:35

Хорошо, я посмотрю репозиторий, который вы добавили позже сегодня. В принципе ссылки на репо не рекомендуются, потому что они имеют тенденцию устаревать после ответа на вопрос и внесения исправления в проект. Рекомендуемое решение — предоставить минимальный воспроизводимый пример (codesandbox.io или аналогичный).

tao 08.05.2023 11:57

Ты прав! Я добавил к вопросу ссылку на песочницу, потому что она слишком длинная для комментария.

leonheess 08.05.2023 20:24

Я не смог заставить useTestingPinia() работать, и я не совсем понимаю, почему. Но он отлично работает с правильным хранилищем (например, передайте createPinia() плагинам теста). Вот оно. Если вы хотите разобраться, почему createTestingPinia() не работает, возможно, вам следует спросить в репозитории pinia. Мне никогда не приходилось издеваться над магазином с тех пор, как я начал использовать пинию, я просто экспортирую pinia = createPinia() + все магазины из папки ./store.

tao 09.05.2023 14:09

Когда я пытаюсь использовать useTestingPinia() в вашем примере, я получаю store.init не является ошибкой функции. По понятным причинам я обновил ответ кодом, который действительно работает.

tao 09.05.2023 14:13
Ответ принят как подходящий

Проблема в том, что свойство createSpy ожидает функцию, но был предоставлен вызов. Измените vi.fn() на vi.fn, и все заработает, как и ожидалось.

createTestingPinia({
  createSpy: vi.fn, <--
  ...
})

Источник: https://pinia.vuejs.org/cookbook/testing.html#specifying-the-createspy-function

Хорошая находка. Вы предлагаете createI18n() плагин, предоставленный для монтирования, не нужен? На самом деле это не так, потому что вы издеваетесь над всем модулем. Прохладный.

tao 10.05.2023 20:41

Другие вопросы по теме