22

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!

7 Answers 7

10

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
};
Sign up to request clarification or add additional context in comments.

7 Comments

Still getting TypeError: $setup.t is not a function
@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.
In my setup script I do import { useI18n } from 'vue-i18n'; const { t } = useI18n();.
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. vitest.dev/guide/mocking.html#modules
@Luckylooke I have the same problem. $setup comes from the composition api
|
9

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 Comments

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 you need to properly configure vuei18n, check my answer above in case you still need it.
6
+50

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.

4 Comments

@diego thanks for the solution! what if I want to test the text inside "message.success"? I mean expect(wrapper.text()).toContain('Successfull');
@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.
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 Check out this answer: stackoverflow.com/a/76063191/7910454
5
import { createI18n } from 'vue-i18n';

describe('xxx', () => {
   it('yyy', () => {
      const i18n = createI18n({
         messages: {
            gb: {},
            nl: {},
            ...
         }
      });
      
      const wrapper = mount(YourComponent, {
         global: {
            plugins: [i18n]
         }
      });
   }
})

Comments

4

Global solution for composition API:

import { vi } from 'vitest';

vi.mock('vue-i18n', () => ({
  useI18n: () => ({
    t: (key: string) => key,
    d: (key: string) => key,
  }),
}));

Comments

2

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.

Comments

0

And in case you're using Options API + need to mount the app with its i18n plugin, you can always fake the t function at the time of setting up vitest:

const i18n = createI18n({
  locale: 'en',
  fallbackLocale: 'en',
  allowComposition: true,
  messages: {}
})
/* "override" $t function just for testing components, or use vi.fn(k => k)
if need to spy on it */
i18n.global.t = tKey => tKey

config.global.plugins = [i18n]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.