Reference a computed property in a jest unit test - vue.js

I'm writing a jest unit test for my Vuejs 2 (Vuetify) component. Setup is pretty simple.
Inside the component I have a prop, which is an object with nested fields:
props: ['testObject']
And I have a computed property that returns a field from that prop:
computed: {
myField() {
return this.testObject.testField;
}
}
And I write a Jest test:
wrapper = mount(MyComponent, {
mocks: {
propsData: {
testObject: { testField: 'My Test Value' }
}
},
});
it("should return a test field", () => {
const field = wrapper.vm.$options.computed.myField();
expect(field).toBe('My Test Value');
});
When I run the test I get the following:
TypeError: Cannot read property 'testField' of undefined
So it calls computed propperty, but testObject is undefined.
Any idea what's going on?
#vue/test-utils: 1.3.0
#vue/vue2-jest: 28.0.1
#types/jest: 28.1.3
jest: 28.1.1

Your code failing because the this inside the computed function referencing $options. You need to access computed as a vm's attribute, not the $option. Just do this.
expect(wrapper.vm.myField).toBe('My Test Value');
But there is one more problem here. Actually you don't need to test the computed value. Because if you accidentally remove this computed value rendering from template, this tests will still be passed. Instead, test what this string is rendered in html.
expect(wrapper.text()).toContain('My Test Value');

Try to mount your component this way:
wrapper = mount(MyComponent, {
propsData: {
testObject: { testField: 'My Test Value' }
}
});
Check this documentation for more informations.

Alright, I worked it out by doing this:
it("should return a test field", async () => {
wrapper.vm.$options.computed.testObject.call({ testField: 'My Test Value' });
...
});
Hope that will help for those who experience the same issue.

Related

How to initialize data with computed value inside asyncData?

I am building a web app with nuxt.
here's simplified code:
pages/index.vue
data() {
return {
item: {name:'', department: '', testField: '',},
}
}
async asyncData() {
const result = call some API
const dataToInitialize = {
name: result.username,
department: result.department,
testField: //want to assign computed value
}
return {item: dataToInitialize}
}
Inside asyncData, I call API and assign value to dataToInitialize.
dataToInitialize has testField field, and I want to assign some computed value based on username and department.
(for example, 'a' if name starts with 'a' and department is 'management'..etc there's more complicated logic in real scenario)
I have tried to use computed property , but I realized that asyncData cannnot access computed.
Does anyone know how to solve this?
Any help would be appreciated!
=======
not sure if it's right way, but I solved the issue by setting 'testfield' inside created.
created() {
this.item.testField = this.someMethod(this.item);
},
Looking at the Nuxt lifecyle, you can see that asyncData is called before even a Vue instance is mounted on your page.
Meanwhile, fetch() hook is called after. This is non-blocking but more flexible in a lot of ways.
An alternative using fetch() would look like this
<script>
export default {
data() {
return {
staticVariable: 'google',
}
},
async fetch() {
await this.$axios(this.computedVariable)
},
computed: {
computedVariable() {
return `www.${this.staticVariable}.com`
},
},
}
</script>
Another alternative, would be to use URL query string or params, thanks to Vue-router and use those to build your API call (in an asyncData hook).
Here is an example on how to achieve this: https://stackoverflow.com/a/68112290/8816585
EDIT after comment question
You can totally use a computed inside of a fetch() hook indeed. Here is an example on how to achieve this
<script>
export default {
data() {
return {
test: 'test',
}
},
async fetch() {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${this.nice}`)
console.log(await response.json())
},
computed: {
nice() {
return this.test + 'wow!'
},
},
}
</script>
I found that destructuring fetch({}) causes issues with accessing this inside fetch scope ->
async fetch({ store, $anyOtherGlobalVar }){
store.dispatch...
// destructuring approach changes the scope of the function and `this` does not have access to data, computed and e.t.c
}
If you want to access this scope for example this.data, avoid destructuring and access everything through this.
async fetch() {
this.$store...
this.data...
}

Nuxt - How to call a getters in a global mixins?

Hi everyone here is the mixin code I wrote as I want to use this for default.vue and error.vue layout. I am trying to avoid duplicating code in two layout.
export default {
provide () {
return {
copyRight: this.getCopyrightText,
email: this.getEmail,
socials: this.getSocials
}
},
computed: {
getMenu () {
return this.store.getters['general/getMenu'].menu
},
getSocials () {
return this.store.getters['general/getSocialDetails']
},
getCopyrightText () {
return this.store.getters['general/getCopyRight']
},
getEmail () {
return this.store.getters['general/getEmail']
}
},
middleware: 'load-menu-items'
}
This is what I get: Cannot read property 'length' of undefined
What am I doing wrong?
In your component I assume you're using .length on the data you're receiving from the getter method, which is probably where the error occurs.
First of all you should debug to see if your getter is actually working as expected. Try this and look at output in console for every getter computed property. If undefined is printed to the console you'll get the error you posted if you're using .length on it
getEmail () {
let data = this.store.getters['general/getEmail'];
console.log(data);
return data;
}
If you post the component which is using this mixin maybe I can help you further.

How Test with Jest a function in the method "mounted" VueJS

I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for themethods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.

Vue Test Utils - Skip created hook

I want to skip all of the methods that are being called within the created() hook. Is there a way to do this?
So instead of this
created() {
this.getAllocations();
this.getModels();
this.getTeams();
this.getCustodians();
this.getDefaultFeeStructure();
}
I want this
created() { }
It's worth noting, I cannot actually change the component itself, but for testing purposes, this needs to be done.
You can accomplish this with a global mixin (see https://v2.vuejs.org/v2/guide/mixins.html#Global-Mixin)
However, for your case you need a custom merge strategy to prevent the created hook on the component from being run:
Hook functions with the same name are merged into an array so that all of them will be called. Mixin hooks will be called before the component’s own hooks. (https://v2.vuejs.org/v2/guide/mixins.html#Option-Merging)
See a working example at https://jsfiddle.net/rushimusmaximus/9akf641z/3/
Vue.mixin({
created() {
console.log("created() in global mixin")
}
});
const mergeCreatedStrategy = Vue.config.optionMergeStrategies.created;
Vue.config.optionMergeStrategies.created = (parent, child) => {
return mergeCreatedStrategy(parent);
};
new Vue ({
el: "#vue-app",
template: '<p>See console output for logging. Rendered at {{renderDate}}</p>',
data() {
return {
renderDate: new Date()
}
},
created() {
console.log("created() in component")
}
})

How to pass variable from Vuex store to a function as value?

So I am working with what would appear to be a simple issue, but it is eluding me this evening. I have a value that is set in a Vuex store. In my component file, I declare a constant where the value is retrieved from the store. Everything up to this point works perfectly.
Then, upon submitting a form in the component a script function is run. Within that function, I need to pass the value from the Vuex store along with a couple of other arguments to another function. The function gets call, the arguments are passed, and it all works as expected.
However ... I am getting console errors stating ...
Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] do not mutate vuex store state outside mutation handlers.
What is the correct what to retrieve a value from the Vuex store and then pass that value to a function?
Some more detail here ... Page 1 stores an object representing a CognitoUser in the store using a mutation function which works as expected, then transitions to Page 2. Page 2 retrieves the object from the store (tried both the data and computed methods mentioned below as well as using the getter directly in the code - all fail the same). Within a method on Page 2, the object from the store is accessible. However, that method attempts to call the Amplify completeNewPassword method, passing the CongnitoUser object as an argument. This is the point that the error appears stating that the mutation handler should be used even though there is no change to the object on my end.
....
computed: {
user: {
get(){
return this.$store.getters[ 'security/localUser' ]
},
set( value ){
this.$store.commit( 'security/setLocalUser', value )
}
}
},
....
methods: {
async submitForm(){
this.$Amplify.Auth.completeNewPassword( this.user, this.model.password, this.requiredAttributes )
.then( data => {
....
This is almost certainly a duplicate question. You can refer to my answer here.
Basically you should pass the Vuex value to a local data item and use that in your component function. Something like this.
<script>
export default {
data: () => ({
localDataItem: this.$store.getters.vuexItem,
})
methods: {
doSomething() {
use this.localDataItem.here
}
}
}
</script>
The canonical way of handling this by using computed properties. You define a computed property with getter and setter and proxy access to vuex thru it.
computed: {
localProperty: {
get: function () {
return this.$store.getters.data
},
set: function (val) {
this.$store.commit(“mutationName”, val )
}
}
}
Now you can use localProperty just as we use any other property defined on data. And all the changes get propagated thru the store.
Try if this work
<template>
<div>
<input :value="user" #change="onChangeUser($event.target.value)"></input>
</div>
</template>
<script>
computed: {
user() {
return this.$store.getters[ 'security/localUser' ]
}
},
methods: {
onChangeUser(user) {
this.$store.commit( 'security/setLocalUser', user );
},
async submitForm(){
this.$Amplify.Auth.completeNewPassword( this.user, this.model.password, this.requiredAttributes )
.then( data => {
...
}
</script>