Open Mikilll94 opened 3 years ago
@kaorun343 https://github.com/vuejs/vue-test-utils/issues/1767#issuecomment-761301172
As you can see this is rather a bug in vue-class-components
than in vue-test-utils
.
Does this work in a browser with the real $t
? The problem is only when mocking $t
?
@lmiller1990 In browser it is working. You can check this in my repro project. Only unit tests are failing.
I am guessing VCC does some transform first, then VTU applies the mock $t
, at which point it is too late.
I would recommend using data
or computed
as a work-around in the meantime.
Are you interested in investigating this bug/making a PR?
@lmiller1990
In the meantime I was debugging this for a while. It seems that vue-class-component
overrides the class constructor and transforms property initializers into data
in Vue component. This is happening in this function:
https://github.com/vuejs/vue-class-component/blob/16433462b40aefecc030919623f17b0ec9afe61c/src/data.ts#L5
If you debug vm
variable you can see that when running unit tests, it is missing _i18n
property. In the browser this property is present.
BTW. accessing this
stuff in property initializers is a valid functionality of VCC. You check this here:
https://github.com/vuejs/vue-class-component/issues/432
https://github.com/vuejs/vue-class-component/pull/434
Interesting. I suppose this makes sense. Class Component -> VCC transform -> VTU $mocks. By the time $mocks happens, it is too late and the component is using whatever this.$i18n
was at the time of the transform. It expects $t
to exist when VCC does it's transform, since it doesn't, boom.
I do not see any way we can fix this in VTU - whatever we do, it's going to be too late to get the mocked value in. Seems VTU v2 and the next version of class component exhibit the same bug for the same reason.
An alternative would be using a createLocalVue
, where you install a mocked $i18n
. A bit more boilerplate, but you could make a function. Does that make sense?
@lmiller1990
Interesting. I suppose this makes sense. Class Component -> VCC transform -> VTU $mocks. By the time $mocks happens, it is too late and the component is using whatever this.$i18n was at the time of the transform. It expects $t to exist when VCC does it's transform, since it doesn't, boom.
When the mocking exactly happens in vue-test-utils? (you can show this in the source code) Does mocking occur after creating a component? If yes, then this is problematic for class components, because this Component.prototype._init
function is executed before creating a component. This function transforms the property initializers to the data
object.
Could you check the VCC source code to see if there is any solution to this? Maybe this can be fixed in VCC source code?
An alternative would be using a createLocalVue, where you install a mocked $i18n. A bit more boilerplate, but you could make a function. Does that make sense?
Could you show an example?
The mocking happens here. createInstance
is ultimately called when mount is called. The class component transform is applied far before this - it's all compiled to a regular object before it's even passed into mount
.
I don't see a way to fix this by patching VCC.
Could you show an example
Looks like my idea doesn't work. I thought I could do something like this:
class MockPlugin {
static install(Vue: any) {
Vue.$t = (key: string) => key
}
}
const localVue = createLocalVue()
localVue.use(MockPlugin)
describe('App.vue', () => {
it('some unit test', () => {
const wrapper = mount(App, {
localVue
});
expect(wrapper).toBeDefined();
})
})
But the result is the same. Hm. I think something else is at work here... I cannot even get this to work with the real i18n plugin.
Ok... this worked (webpack + mocha combo).
import { expect } from 'chai'
import Vue from 'vue'
import { mount, createLocalVue } from '@vue/test-utils'
import App from '@/App.vue'
class i18n {
static install(Vue: any) {
Vue.prototype.$t = (key: string) => key
}
}
Vue.use(i18n)
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const wrapper = mount(App)
console.log(wrapper.html())
})
})
But this does not:
import { expect } from 'chai'
import Vue from 'vue'
import { mount, createLocalVue } from '@vue/test-utils'
import App from '@/App.vue'
class i18n {
static install(Vue: any) {
Vue.prototype.$t = (key: string) => key
}
}
const localVue = createLocalVue()
localVue.use(i18n)
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const wrapper = mount(App, {
localVue
})
console.log(wrapper.html())
})
})
I guess when VCC does it's thing, Vue
already has $t
. It does not work with localVue
- VCC does not know about localVue
. I don't see any way to "tell" VCC which Vue instance to use, so this is likely out of the question.
If you dont't mind using a mutated global Vue for all your tests, you could do something like I've shown here (making a "fake" i18n
plugin).
I am still not sure on how we can make mocks
work with VCC. Let's experiment a bit more.
A repro excluded the dep to vue-test-utils would be:
@Component
class Base extends Vue {
test() {
return 'base'
}
}
@Component
class Test extends Base {
result = this.test()
}
const vm = new Test()
expect(vm.result).to.equal('base')
const Replaced = Vue.extend()
Replaced.prototype.test = () => 'replaced'
const replaced: Test = new (Replaced.extend({
...(Test as any).options,
}))()
expect(replaced.result).to.equal('replaced')
We could replace the prototype of the original constructor with the one of localVue
in collectDataFromConstructor
but I'm not very sure about it and if it is really a good idea. I'll need more time to take a look...
I thought about this too, but doesn't feel like a great solution.
We should also think about how this works in vue-class-component-next and vue-test-utils-next. vue-test-utils-next has the exact same problem... but does not use the localVue
. We should think of a solution that will work there, too.
hope it helps you.
@Component
export default class App extends Vue {
private someData();
created() {
this.someData = this.$t("message");
}
}
@ktsn Please take a look at these comments: https://github.com/vuejs/vue-test-utils/issues/1767#issuecomment-767597984 https://github.com/vuejs/vue-test-utils/issues/1767#issuecomment-769596262
Subject of the issue
I have the following component
and the following unit test
When running this unit test, it fails with the following error:
Steps to reproduce
Just run unit tests in this project: i18n-test.zip
Expected behaviour
Error
this.$t is not a function
should not occur. The$t
function is mocked.Actual behaviour
An error
this.$t is not a function
is thrown.Additional context
If I convert
someData
to computed property, like this:the error is gone.