Closed Mrkisha closed 6 years ago
Hi @Mrkisha , I work on test utils a lot, and use i18n in my apps. You can just do
const wrapper = shallow(MyComponent, {
mocks: {
$t: () => {}
}
})
and you should be fine, no warning.
If you want something to show you can do
const wrapper = shallow(MyComponent, {
mocks: {
$t: () => 'some specific text'
}
})
@lmiller1990 Thank you. It works.
@Mrkisha PS we added mocks
to the global config recently in @vue/test-utils
, which I assume you are using. So if you install the latest @vue/test-utils
, you can do:
import VueTestUtils from '@vue/test-utils'
VueTestUtils.config["$t"] = () => {}
If you are using Jest, you would do this is setupFiles.
See here for the config
options: https://vue-test-utils.vuejs.org/en/api/config.html
@lmiller1990 Thanks for the new tip! However I think you made a typo in your example, it should be:
VueTestUtils.config.mocks["$t"] = () => {}
Sometimes it's useful to check which wording is displayed (like for an error message), so in my projects I use:
VueTestUtils.config.mocks.$t = key => key
Thank you all. Additional note for those that use v-t directive. You can stub it like this:
import Vue from 'vue';
Vue.directive('t', () => {});
This works as long as it's shallow
. If you mount
the component and any subcomponent uses translations the tests will fail, even if you have a global mock. I wish there was something we could do to solve this issue, because we've had a lot of problems with [Vue warn]: Error in render: "TypeError: _vm.$t is not a function"
.
@Laruxo careful doing that. You will avoid polluting the global Vue instance. Consider using localVue
:
import { createLocalVue } from "@vue/test-utils"
const localVue = createLocalVue()
localVue.directive("t", () => {})
const wrapper = shallowMount(Foo, {
localVue
})
@cesalberca can you provide a minimal repro of the bug you described? If not I can try to do and post an issue in vue-test-utils. I contribute to
vue-test-utils` and was you described sounds like a bug we should fix. Some problems related to child-child components will be fixed here https://github.com/vuejs/vue-test-utils/pull/840 , not sure if that will ficu your problem though.
@lmiller1990, ok, upon further investigation it seems this gets solved in 1.0.0-beta.21
. However a new message appears in console:
[vue-test-utils]: an extended child component <SubComponent> has been modified to ensure it has the correct instance properties. This means it is not possible to find the component with a component selector. To find the component, you must stub it manually using the stubs mounting option.
This is the repo: https://github.com/cesalberca/i18n-typescript-tests-bug
Try updating @vue/test-utils from 1.0.0-beta.20
to 1.0.0-beta.21
to see the test pass and the message appear. So glad that at leas the error is solved, because it was a truly a pain to deal with. The only thing left to do is to know how to deal with that message.
The message is to warn you that you can't access the component by using the constructor as a selector. You can hide it by setting logModifiedComponents
to false:
import { config } from '@vue/test-utils'
config.logModifiedComponents = false
We'll improve the message to make it clear what is happening.
The support for extended components (like TS components) is going to be improved in beta.23.
So glad to hear that @eddyerburgh! I just noticed that there is no export default anymore, which makes TS fail when importing from @vue/test-utils
:
P.R incoming ;)
What about this?
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Buefy from 'buefy';
import TheLogin from '@/components/TheLogin.vue';
import { setupI18n } from './i18n';
const localVue = createLocalVue();
localVue.use(Buefy);
const i18n = setupI18n(localVue);
describe('TheLogin.vue', () => {
const wrapper = shallowMount(TheLogin, {
localVue,
i18n,
});
test('login button is initially disabled', () => {
const loginButton = wrapper.find('.login-button');
expect(loginButton.attributes().disabled).toBe('disabled');
});
});
And the i18n.ts
file:
import { VueConstructor } from 'vue';
import VueI18n, { LocaleMessages } from 'vue-i18n';
function loadLocaleMessages(): LocaleMessages {
const messages = require('@/locales/en.json');
return {
en: messages,
};
}
export function setupI18n(vueInstance: VueConstructor) {
vueInstance.use(VueI18n);
return new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages(),
});
}
Otherwise, I'm totally stumped on this one. This seems to work, though. @eddyerburgh - thoughts?
@ffxsam what exactly is the problem? Do you get a warning/error?
No problem, I'm just suggesting a solution. (the above works great for me)
import { config, shallowMount } from '@vue/test-utils'
import BaseDialog from '@/components/BaseDialog/BaseDialog'
config.mocks.$t = key => key
describe('BaseDialog', () => {
it('is called', () => {
const wrapper = shallowMount(BaseDialog, {
attachToDocument: true
})
expect(wrapper.name()).toBe('BaseDialog')
expect(wrapper.isVueInstance()).toBeTruthy()
})
})
And I get:
TypeError: Cannot read property '$t' of undefined
at VueComponent.default (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/src/components/BaseDialog/BaseDialog.vue:2671:220)
at getPropDefaultValue (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:1662:11)
at validateProp (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:1619:13)
at loop (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4612:17)
at initProps (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4643:33)
at initState (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4586:21)
at VueComponent.Vue._init (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4948:5)
at new VueComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5095:12)
at createComponentInstanceForVnode (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3270:10)
at init (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3101:45)
at createComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5919:9)
at createElm (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5866:9)
at VueComponent.patch [as __patch__] (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:6455:9)
at VueComponent.Vue._update (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3904:19)
at VueComponent.updateComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4025:10)
at Watcher.get (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4426:25)
at new Watcher (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4415:12)
at mountComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4032:3)
at VueComponent.Object.<anonymous>.Vue.$mount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:8350:10)
at mount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/@vue/test-utils/dist/vue-test-utils.js:8649:21)
at shallowMount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/@vue/test-utils/dist/vue-test-utils.js:8677:10)
at Object.it (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/src/components/BaseDialog/__tests__/BaseDialog.spec.js:8:21)
at Object.asyncJestTest (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/jasmine_async.js:108:37)
at resolve (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:56:12)
at new Promise (<anonymous>)
at mapper (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:43:19)
at promise.then (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:87:41)
at process.internalTickCallback (internal/process/next_tick.js:77:7)
I've tried A LOT of stuff on the webs to get this to work and no matter what I get the same error.
@mmelvin0581 can you share BaseDialog
?
What happens if you do
shallowMount(BaseDialog, {
mocks: {
$t: key => key
}
})
Sorry for the alarm guys. Stupid mistake on my part. I was referencing ‘this’ inside of a prop validator...
This is how I've solved it.
In a test file
import { mount } from '@vue/test-utils'
// Can be changed to
import { mount } from '../myVueTestUtils.js'
// And you can use mount or shallowMount as usual
const wrapper = mount(DocumentEditModal, { propsData: { document_id: '123' } })
In myVueTestUtils.js
import { mount as originalMount } from '@vue/test-utils'
import { i18n } from 'utils/i18n'
// wrap the @vue/test-utils mount method and extend it with i18n
export function mount(component, opts = {}) {
return originalMount(component, { ...opts, i18n })
}
Then you can use your i18n as usual without mocking or doing a complicated localVue setup, or adding to the shallow/mount functions every time in every test file.
I've created a repository explaining it here: https://github.com/Illyism/vue-i18n-jest-example
@Illyism What is this import you propose?
import { i18n } from 'utils/i18n'
The solution you propose looks great, but I can't make this import work.
Hello,
im my case i do want to have real translations,
so i'm importing my getI18n
function (the same function that the real app is calling) :
[...]
export default function () {
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: defaultLocale,
fallbackLocale: 'en',
messages: messages
});
return i18n;
};
and then :
import {shallowMount, config} from '@vue/test-utils';
import getI18n from 'src/utils/get-i18n';
const i18n = getI18n();
config.mocks['i18n'] = i18n;
config.mocks["$t"] = (key: string) => {
return i18n.t(key);
}
and then i'm using shallowMount to mount my component. You can also instantiate i18n inside your test direclty if you do not want to have a function.
Same error TypeError: _vm.$t is not a function Error from nested child of mount component Tried every solution in this topic
@Jeerjmin @DktRemiDoolaeghe
Take a look here, I've created a simple boilerplate project to explain how it works: https://github.com/Illyism/vue-i18n-jest-example
You can see the unit test runs here: https://github.com/Illyism/vue-i18n-jest-example/runs/441929401#step:4:9
@Illyism I've got a quick glance of what you've done and decided to adapt it to my project. Definitely a good idea 👍
Hello
I am using this.$t(...)
in a sub-component AppModalDialog in the created() method.
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Error in created hook: "TypeError: this.$t is not a function"
created() {
const label = this.$t('app-modal.button').toString();
}
I am already mocking $t it with
const wrapper = shallowMount(UpperComp, {
localVue,
$t: () => 'Some label',
}
in UpperComp.
Also, I am wondering why it is necessary alltogether as i use shallowMount here. Can it be related to using the modal component lazy-loaded with AppModalDialog: () => import('@/components/shared/AppModalDialog.vue')
?
@matthiassommer It sounds like in the AppModalDialog you're using your own instance of Vue (for example Vue.extend()) that is causing that error and it's causing the modal to be mounted anyway. You could try mocking the AppModalDialog in your code to see what happens.
It depends a lot on your implementation. If you could share you code or make a minimal version somehow I could take a look.
I am instantiating the component with
export default Vue.extend({
name: 'app-modal-dialog',
Is there any benefit or need to use Vue.extend instead of just export default {} ?
I am mocking it with jest.mock('@/components/shared/AppModalDialog.vue', () => ({ name: 'app-modal-dialog' }));
But now I get
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <AppModalDialog>
@matthiassommer I'm having the same issue _vm.$t
is used in templates and mocked correctly by passing in mocks
as mentioned above, but I'm also using this.$t
in the code which is not solved by this. Did you find an elegant solution?
I am testing a script which is a mixin. So this is what worked for me:
import myMixin from "../../../src/mixins/myMixin";
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import { ENGLISH_TRANSLATIONS } from '../../../src/translations/en';
Vue.use(VueI18n);
const TRANSLATIONS = {
en: ENGLISH_TRANSLATIONS
};
const i18n = new VueI18n({
locale: 'en',
messages: TRANSLATIONS,
});
in the it scope:
it("testing stuff", () => {
// arrange
const wrapper = {
$t: jest.fn((key) => i18n.t(key)),
};
}
@matthiassommer @jsborjesson How did you solve the problem with mocking the this.$t
please? It would be very helpful if you shared.
@tasha13 I should have not forgotten to reply that here last year, now I can't find it. I may go digging through commits but I do believe I solved it by passing in the real translations to Vue and not mocking that part, similar to above
Here is my solution (I used mocha instead of jest but it is not really matter): use global.mock.$t and create your own implementation
import { expect } from 'chai' //mocha.js
import { mount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
const $t = () => {}
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = mount(HelloWorld, {
props: { msg },
global: {
mocks: {
$t
}
}
})
expect(wrapper.text()).to.include(msg)
})
})
@soboltatiana @matthiassommer @jsborjesson Anyone solved issue with nested components and $t?
I'm using Vue.extend({}) with nested component and the same error this.$t is undefined.
This problem made me pull out my hair far too many times. I couldn't test functionality without mounting few components at once, and I was left with testing each component separately. That can be really brittle and hard to maintain.
The problem is related to another issue I had a long time ago with unwanted console logs in tests.
Components that use Vue.extend()
are importing Vue from the source. we are using localVue
, but it seems to be used only for component that you are shallow mounting in test, every child component is still using Vue directly. This causes various issues, but it's 'hackable'. Maybe this is not a real solution, rather a hack, but it works. It requires a global Vue mock for Jest.
I wrote detailed explanation here, and it's not only about VueI18n, but any plugin that is used by child components that we want to mount in our tests.
Hello, I was wondering how do you handle
Because when I run my tests i get : [Vue warn]: Failed to resolve component: i18n-t, If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
Hi, my solution in actual Vue3/Vite/Vitest environment:
// 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
};
Hello, I was wondering how do you handle component ?
Because when I run my tests i get : [Vue warn]: Failed to resolve component: i18n-t, If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
Never used VueI18n component, but in this case, the only option I see is to create a stub for it and register it in the same place where VueI18nStub
is (from example provided in my previous comment).
Hi everyone,
I faced this issue today and spent a lot of time on time trying all the solutions mentioned here. Unfortunately none worked :-( so I kept "digging" and managed to made it work by setting $t
on beforeCreate
hook:
wrapper = shallowMount(MyComponent, {
propsData: {
...
},
...
beforeCreate() {
this.$t = jest.fn((key: string) => key);
},
});
Hope this is useful.
For Vue3 and Vite I got this working across all unit tests for anything using $t('whatever')
:
// vitest.setup.ts
import { vi } from 'vitest'
import { config } from '@vue/test-utils'
config.global.mocks = {
$t: vi.fn((key: string) => key)
}
// vite.config.ts
export default defineConfig(() => {
return {
//...etc
test: {
setupFiles: ['./vitest.setup.ts'],
}
}
})
Also if you're using the <i18n-t>
component you can add the following to the vitest.setup.ts file:
import i18n from './src/i18n'
config.global.plugins = [i18n]
Which gets the components outputting what they're supposed to in the unit tests.
Hope it helps.
I'm trying to write a test for page that uses vue-i18n plugin. @eddyerburgh has show how to test with mocked plugin. https://github.com/kazupon/vue-i18n/issues/198#issuecomment-316692326 This works fine.
However in some tests I do not care about translations. Instead mocking translations, I'd like to stub them. What ever I do i get
[Vue warn]: Error in render: "TypeError: _vm.$t is not a function"
.