11

I have started to replace Jest with Vitest for my unit test library in my Vue 3 App.

I am trying to write unit test for a component that uses the vue-i18n library to translate text within it but when I try to mount this component in my test file, it fails with the error:

ReferenceError: t is not defined

What is the proper way to stub/mock t from import { useI18n } from 'vue-i18n' when writing tests using the vitest library?

Note since upgrading from Vue2 to Vue3 this does not work:

const wrapper = shallowMount(MyComponent, {
  global: {
    mocks: {
      $t: () => {}
    }
  }
})

Here is a list of some notable package versions:

"vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.14",
"vite": "^2.9.0",
"vitest": "^0.10.2"

Thanks!

leonheess
  • 16,068
  • 14
  • 77
  • 112
Mac
  • 1,025
  • 3
  • 12
  • 22

6 Answers6

5

I suppose you want to mock this globally, no need to put same code in every test suite.

// vitest.config.ts
import { mergeConfig } from 'vite';
import { defineConfig } from 'vitest/config';
import viteConfig from './vite.config';

export default defineConfig(
    mergeConfig(viteConfig, { // extending app vite config
        test: {
            setupFiles: ['tests/unit.setup.ts'],
            environment: 'jsdom',
        }
    })
);
// tests/unit.setup.ts
import { config } from "@vue/test-utils"

config.global.mocks = {
  $t: tKey => tKey; // just return translation key
};
Luckylooke
  • 4,061
  • 4
  • 36
  • 49
  • 2
    Still getting `TypeError: $setup.t is not a function` – leonheess Dec 15 '22 at 01:16
  • @leonheess please provide more context, as I have no clue where $setup comes from, but you may notice that we are mocking `$t` not `t` here, maybe that's the cause of your issue. Try to replace it on either side. – Luckylooke Dec 21 '22 at 09:10
  • 1
    In my setup script I do `import { useI18n } from 'vue-i18n'; const { t } = useI18n();`. – leonheess Jan 26 '23 at 15:26
  • 1
    As far as I understand, in the question, they want to mock globally injected 't' in templates. You seems to want mock the libary aka useI18n module itself, you can do it with vi.mock() method, same as it was jest.mock() in jest. https://vitest.dev/guide/mocking.html#modules – Luckylooke Jan 30 '23 at 12:06
  • 1
    @Luckylooke I have the same problem. `$setup` comes from the composition api – noel293 Feb 14 '23 at 15:18
  • Ok, probably composition API but not setup script, I assume.. because I have never face similar error so far. And if your global mock match the case you are using `t` or `$t` then I have no clue why it does not work for you, sorry ‍♂️ – Luckylooke Feb 15 '23 at 19:14
  • @leonheess you need to properly configure vuei18n for the composition api. Check my answer above in case you still need it. – briosheje Apr 06 '23 at 14:14
4
import { createI18n } from 'vue-i18n';

describe('xxx', () => {
   it('yyy', () => {
      const i18n = createI18n({
         messages: {
            gb: {},
            nl: {},
            ...
         }
      });
      
      const wrapper = mount(YourComponent, {
         global: {
            plugins: [i18n]
         }
      });
   }
})
Panos Vakalopoulos
  • 515
  • 1
  • 8
  • 23
4

Panos Vakalopoulos’s answer worked for me.

And the code could be run globally.

See https://test-utils.vuejs.org/migration/#no-more-createlocalvue

// vite.config.ts
export default defineConfig(
    // add config for test
    test: {
        environment: 'jsdom',
        setupFiles: 'vitest.setup.ts',
    }
);

// vitest.setup.ts'
import { config } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'
const i18n = createI18n()
config.global.plugins = [i18n]
// YourComponent.vue
<div id="app">
    <p>{{ t("message.hello") }}</p>
</div>

<script lang="ts" setup>
    import { useI18n } from 'vue-i18n'
    const { t } = useI18n()
</script>
// component_test.ts
describe('xxx', () => {
    it('yyy', () => {
        const wrapper = mount(YourComponent);
    }
})

Note that if you use global config as $t, Luckylooke's answer would work.

// YourComponent.vue
<div id="app">
    <p>{{ $t("message.hello") }}</p>
</div>
// tests/unit.setup.ts
import { config } from "@vue/test-utils"

config.global.mocks = {
    $t: tKey => tKey; // just return translation key
};
  • 2
    what about when using composition api on your app? I followed your steps but I am getting `TypeError: $setup.t is not a function` error back. – noel293 Feb 14 '23 at 15:16
  • @noel293 you need to properly configure vuei18n, check my answer above in case you still need it. – briosheje Apr 06 '23 at 14:13
1

I read this tutorial that teaches to mock vue-router, then I made a similar solution for vue-i18n and it worked.

Component (HelloWorld.vue)

<script setup>
import { useI18n } from "vue-i18n";

const { t } = useI18n();
</script>

<template>
  <div class="greetings">
    <h1>{{ t("commonsmessagehello") }}</h1>
    <h2>{{ t("localhello") }}</h2>
    <h2>{{ $t("message.success") }}</h2>
  </div>
</template>

<i18n src="../commons/locales.json"></i18n>
<i18n>
{
  "enUS": {
    "localhello": "local helloooooo"
  }
}
</i18n>

Test

import { describe, it, expect, vi } from "vitest";
import { mount, config } from "@vue/test-utils";
import { useI18n } from "vue-i18n";
import HelloWorld from "../HelloWorld.vue";

vi.mock("vue-i18n");

useI18n.mockReturnValue({
  t: (tKey) => tKey,
});

config.global.mocks = {
  $t: (tKey) => tKey,
};

describe("HelloWorld", () => {
  it("renders properly", () => {
    const wrapper = mount(HelloWorld, { });
    expect(wrapper.text()).toContain("message.success");
  });
});

How you can see, it worked for t and $t.

That's not the ideal way. Someday I'll try to figure out how to do it globally for every test.

Diogo Dias
  • 69
  • 3
  • @diego thanks for the solution! what if I want to test the text inside "message.success"? I mean `expect(wrapper.text()).toContain('Successfull');` – Bhagya Swamy Mar 28 '23 at 20:20
  • @BhagyaSwamy theoretically you need to load vue-i18n similarly to what you do in your app. However, I would say _"nobody"_ wants it, because you would be testing vue-i18n, instead of testing your logic/component. – Diogo Dias Mar 29 '23 at 11:46
  • This is the only thing that worked for me. The global solution worked for $t() but not for t(). What's worse is that the error message was complaining about another import missing, it didn't mention i18n. If you have tried the global solution first via setupFiles, you have to remove it. It won't work together. – Suzana Apr 19 '23 at 11:56
  • @Suzana Check out this answer: https://stackoverflow.com/a/76063191/7910454 – leonheess Apr 20 '23 at 10:41
1

Global solution for composition API:

import { vi } from 'vitest';

vi.mock('vue-i18n', () => ({
  useI18n: () => ({
    t: (key: string) => key,
    d: (key: string) => key,
  }),
}));
leonheess
  • 16,068
  • 14
  • 77
  • 112
0

In case you are using the composition API and getting $setup.t is not a function, this is because you are probably wrongly configuring the createI18n instance in your test setup:

import { config } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  legacy: false,
  allowComposition: true
})
config.global.plugins = [i18n]

Note that you need to add legacy: false and allowComposition: true in order to use the composition api, otherwise $setup.t is not going to be defined and you will get the $setup.t is not a function error.

briosheje
  • 7,356
  • 2
  • 32
  • 54