Function:
(Update: Because of the other code I need it to be async/await, otherwise my other code doesn't work)
async getAll() {
request_url = "http://localhost:8082/test"
await axios
.get(request_url, {
headers: { 'Access-Control-Allow-Origin': '*' },
})
.then(response => {
this.all= response.data
})
.catch(error => {
this.errorMessage = error.message
})
},
Test:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import Map from '#/views/Map'
import Vue from 'vue'
import Vuetify from 'vuetify'
import moxios from 'moxios'
import axios from 'axios'
import flushPromises from 'flush-promises'
jest.mock('axios');
Vue.use(Vuetify)
let vuetify
let wrapper
vuetify = new Vuetify()
const localVue = createLocalVue()
describe('View', () => {
beforeEach(() => {
jest.clearAllMocks();
moxios.install()
wrapper = shallowMount(Map, {
localVue,
vuetify,
})
// I need this because I call another function with axios on mounted
flushPromises()
})
it('should get all', async () => {
axios.get = jest.fn().mockResolvedValue({
data: [
{
test: 'test'
}
]
});
await wrapper.vm.getAll().then(() => {
expect(wrapper.vm.all).toEqual(
test: 'test'
)
})
})
Result:
Expected value to equal:
{"test": "test"}
Received:
undefined
I've tried with promises, with moxios, with sinon, nothing seems to work. When I log the this.all in the function, it has the correct value. However, in the test it doesn't wait for the function to finish and for the value to be assigned to this.all. I tried with nextTick, with flushPromises, with await. How do I make the test wait until the function getAll() has assigned this.all the response data? Thank you in advance.
Your method getAll() should be a promise, because "axios" is and returns a promise.
So.. if getAll is a promise, you can call it next with .then().catch()..
Remember return in your promise the resolve() when everyting the tasks ends, or rejection() if your promise ends with errors.
Related
I'm playing around with Vitest and want to wait for the completion of a couple mocked fetches in the onMounted lifecycle hook in my component:
My test:
import { mount } from '#vue/test-utils';
import HelloWorld from './HelloWorld.vue';
import { mockGet } from 'vi-fetch';
import 'vi-fetch/setup';
mockGet('api/welcome-message').willResolve('Welcome message from vitest');
mockGet('api/players').willResolve(['Mario', 'Luigi']);
test('the players have been rendered', async () => {
const wrapper = mount(HelloWorld);
const lastPlayer = await wrapper.findAll('.player');
expect(lastPlayer).toHaveLength(2);
});
My component script:
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const apiMessage = ref('');
const players = ref<string[]>([]);
onMounted(async () => {
const fetchMessage = fetch('api/welcome-message')
.then((res) => res.text())
.then((message: string) => (apiMessage.value = message));
const fetchPlayers = fetch('api/players')
.then((res) => res.json())
.then((playersRes: string[]) => (players.value = playersRes));
});
</script>
The test fails because, I assume, the code running in onMounted doesn't have time to complete before the test looks for all .player <li> elements (rendered with a v-for) off of the players ref. How can I ask vitest to wait for the responses from each of these fetches before calling the test a failure.
Thanks.
The fetch Promises resolve in the next macro tick, which can be awaited like this:
test('...', async() => {
⋮
await new Promise(r => setTimeout(r));
})
Or you can use Vue Test Utils' utility for this:
import { flushPromises } from '#vue/test-utils';
test('...', async() => {
⋮
await flushPromises();
})
Add that line before running any assertions:
👇
import { mount, flushPromises } from '#vue/test-utils';
import HelloWorld from './HelloWorld.vue';
import { mockGet } from 'vi-fetch';
import 'vi-fetch/setup';
mockGet('api/welcome-message').willResolve('Welcome message from vitest');
mockGet('api/players').willResolve(['Mario', 'Luigi']);
test('the players have been rendered', async () => {
const wrapper = mount(HelloWorld);
👇
await flushPromises();
const lastPlayer = await wrapper.findAll('.player');
expect(lastPlayer).toHaveLength(2);
});
demo
I have a vue component that makes use of the store Vuex. However I get a
TypeError: Cannot read property 'token' of undefined
error. I don't understand why. This is my code:
In main.js:
import Vue from 'vue'
import Vuex from 'vuex';
import App from './App.vue'
import router from './router';
import "./assets/css/tailwind.css";
import '#/assets/css/tailwind.css';
import store from './store';
Vue.config.productionTip = false;
Vue.use(Vuex);
new Vue({
router, store,
render: h => h(App),
}).$mount('#app');
In store/indes.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
},
state: {
token: ''
}
})
In GenericForm.vue:
methods: {
execute() {
console.log("GenericForm.vue")
if (this.inputFunction) {
this.inputFunction()
}
this.register()
},
register () {
console.log("register()")
try {
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
})
this.$store.dispatch('setToken', response.data.token)
this.$store.dispatch('setUser', response.data.user)
this.$store.router.push({
name: 'songs'
})
} catch (error) {
console.log(error)
/*
this.error = error.response.data.error
*/
}
}
}
the error occurs on this line of code:
this.$store.dispatch
Any help is appreciated.
EDIT:
AuthenticationService.js
import api from './api'
export default {
register (credentials) {
return api().post('register', credentials)
}
}
api.js
import axios from 'axios'
export default() => {
return axios.create({
baseURL: 'http://localhost:8081'
})
};
After adding console.log:
EDIT2:
New method:
register: async () => {
console.log("register()")
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
}).then((response) => {
console.log(response)
/* this.$store.dispatch('setToken', response.data.token)
this.$store.dispatch('setUser', response.data.user)*/
this.$store.router.push({
name: '/test'
})
});
}
I get the error on
this.$store.router.push({
name: '/test'
})
line:
The response gets logged alright, though.
There are two problems:
First problem:
This code:
register(credentials) {
return api().post('register', credentials)
}
is returning a Promise, which has no data property. What you want is to access the axios response wrapped in that promise, so you either:
call then on the promise
AuthenticationService.register({...}).then((response) => {
console.log(response.data.token) // 'foo'
});
use async/await inside the Vue component
Second problem
The problem that causes the store to be undefined, is the use of the arrow functions. The register() method shouldn't have an arrow. Once the arrow gets removed there is no error (store is defined, as well as a router):
async register() {
console.log("register()")
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
}).then((response) => {
console.log(response)
console.log(this.$store)
this.$router.push({
name: 'ha'
})
});
}
This means that the data property of response is not defined.
Is the AuthenticationService.register method asynchronous?
I'd imagine it is. If so, your code is continuing before the response object has been properly resolved.
Take a second and run console.log(response). You may see an unresolved promise if the method is async.
Otherwise, you may see nothing defined at all if the method does not return anything but instead uses callbacks.
I have the following problem while trying to unit test my Vue application.
Even spying and mocking $router.push, I still can't make it to be called while inside unit testing:
This is my unit testing (Home.spec.js)
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueRouter);
describe('Home.vue', () => {
let actions;
let getters;
let store;
let router;
beforeEach(() => {
actions = {
[FETCH_USER_REPOSITORIES]: jest.fn()
};
getters = {
getTopThreeRepositories: jest.fn(repositoryMock.getRepositories)
};
store = new Vuex.Store({ getters, actions });
router = new VueRouter();
});
it('should redirect when 404 status code received', async () => {
jest.spyOn(store, 'dispatch').mockRejectedValue({ statusCode: 404 });
jest.spyOn(router, 'push').mockResolvedValue({});
const wrapper = await shallowMount(Home, {
store,
localVue,
router
});
expect(router.push).toHaveBeenCalledWith('/not-found');
});
});
Now, this is my Home.vue:
import { mapGetters } from 'vuex';
import { FETCH_USER_REPOSITORIES } from "../store/actions";
import RepositoryList from "#/components/RepositoryList";
import Card from "#/components/Card";
export default {
name: 'view-home',
components: {
Card,
RepositoryList
},
async beforeMount() {
try {
await this.$store.dispatch(FETCH_USER_REPOSITORIES, 'some-repo');
} catch(err) {
console.log(this.$router);
await this.$router.push('/not-found');
}
},
computed: {
...mapGetters(["getTopThreeRepositories"])
}
}
The console log shows the $router correctly, with the spy.
If I force the calling inside the unit testing, it works, but the expects always fails giving me back that $router.push hasn't been called.
Can anyone help me, please?
Thanks!
You should specify $store and $route as mocks in your mounting options, as shown below. Also there's no need to await shallowMount because shallowMount does not return a Promise, so the await would just return immediately.
describe('Home.vue', () => {
it('should redirect when 404 status code received', () => {
const $store = {
dispatch: jest.fn()
}
const $router = {
push: jest.fn()
}
const wrapper = shallowMount(Home, {
localVue,
mocks: {
$store,
$router,
}
});
expect($router.push).toHaveBeenCalledWith('/not-found');
})
})
I have seen similar questions but they dont actually address what am looking for.
so am using using axios globally in app.js for my vue app like window.axios=require('axios')
then in auth.js i have this
export function login(credentials){
return new Promise((res,rej) => {
axios.post('/api/auth/login', credentials)
.then((response) => {
res(response.data);
})
.catch((err) => {
rej("Wrong email or password");
})
});
}
which works fine on the login page
however in my test script
jest.mock("axios", () => ({
post: jest.fn(() => Promise.resolve({data:{first_name:'James','last_name':'Nwachukwu','token':'abc123'}}))
}));
import axios from 'axios'
import {login} from '../helpers/auth'
it("it logs in when data is passed", async () => {
const email='super#gmail.com'
const password='secret';
const result=await login({email,password});
expect(axios.post).toBeCalledWith('/api/auth/login',{"email": "super#gmail.com", "password": "secret"})
expect(result).toEqual({first_name:'James','last_name':'Nwachukwu','token':'abc123'})
})
shows axios is not defined
but if i change auth.js to
import axios from 'axios'
export function login(credentials){
return new Promise((res,rej) => {
axios.post('/api/auth/login', credentials)
.then((response) => {
res(response.data);
})
.catch((err) => {
rej("Wrong email or password");
})
});
}
test passes. how do i run the test without having to import axios on each vue file
I had the same problem just now. I am also including axios via window.axios = require('axios'); in my app.js.
The solution is to set your axios mock on window.axios in your test. So instead of this (incorrect):
axios = {
post: jest.fn().mockName('axiosPost')
}
const wrapper = mount(Component, {
mocks: {
axios: axios
}
})
When your component code calls axios.whatever it is really calling window.axios.whatever (as I understand it), so you need to mirror that in your test environment:
window.axios = {
post: jest.fn().mockName('axiosPost')
}
const wrapper = mount(Component, {
mocks: {
axios: window.axios
}
})
And in your test:
expect(window.axios.post).toHaveBeenCalled()
The above method works fine until you want to chain then to it. In which case you need to set your mock up like this:
window.axios = {
get: jest.fn(() => {
return {
then: jest.fn(() => 'your faked response')
}
}),
}
You don't need to pass it into the component mock though, you can just mount (or shallowMount) the component as usual
I unfortunately can't attach all code or create a gist because the project I'm working on is related to work but I can give enough detail that I think it will work.
I'm trying to mock a call to an action that is stored in a different module but for the life of me I can't figure out how to. I'm able to create a Jest spy on the store.dispatch method but I want to be able to resolve the promise and make sure that the subsequent steps are taken.
The method in the SFC is
doSomething(data) {
this.$store.dispatch('moduleA/moduleDoSomething',{data: data})
.then(() => {
this.$router.push({name: 'RouteName'})
})
.catch(err => {
console.log(err)
alert('There was an error. Please try again.')
})
},
This is what my test looks like:
import { mount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import Vuetify from 'vuetify'
import Component from '#/components/Component'
import moduleA from '#/store/modules/moduleA'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Vuetify)
describe('Component.vue', () => {
let actions
let store
const $router = []
beforeEach(() => {
actions = {
moduleDoSomething: jest.fn((payload) => {
return Promise.resolve({
status: 200,
})
})
}
store = new Vuex.Store({
state: {},
modules: {
moduleA: {
actions
}
},
})
})
it('does something', () => {
const wrapper = mount(Component, {
store,
localVue,
mocks: {
$router,
},
})
let button = wrapper.find('button that calls doSomething')
button.trigger('click')
expect(actions.moduleDoSomething).toHaveBeenCalled()
expect(wrapper.vm.$router[0].name).toBe('RouteName')
})
})
The following test passes, but I don't want to just test that the action was dispatched; I also want to test things in the "then" block.
it('does something', () => {
const dispatchSpy = jest.spyOn(store, 'dispatch')
const wrapper = mount(Component, {
store,
localVue,
mocks: {
$router,
},
})
let button = wrapper.find('button that calls doSomething')
button.trigger('click')
expect(dispatchSpy).toHaveBeenCalledWith('moduleA/moduleDoSomething',{data: data})
})
})
I managed to solve this problem by simply making the module namespaced in the mocked store.
store = new Vuex.Store({
state: {},
modules: {
moduleA: {
actions,
namespaced: true
}
},
})
I'll delete the question in a little bit