Reputation: 445
I've been trying to test files where I am using nuxt's (asyncData and fetch hooks) , I have no problem testing vue.js normal lifecycle but I noticed that vue/test-utils doesn't give clear instructions on how to test nuxt's hooks.
login.vue
asyncData() {
const email = localStorage.getItem("email") || ""
const password = localStorage.getItem("password") || ""
return { email, password }
},
mounted() {
this.setMaxStep()
}
signup.vue
async fetch({ store, redirect, query }) {
const res = await store.dispatch("getSavedFormData")
if (res) {
store.dispatch("setNotification", {
message: "Previous application is loaded"
})
}
},
tried testing it like the following but I get no luck(tried other various things too but I don't know where to look for information)
import {
shallowMount,
config
} from "@vue/test-utils"
import Login from "../../../pages/login
describe("Login", () => {
let wrapper
beforeEach(() => {
wrapper = shallowMount(Login)
})
it("gets asyncData", async () => {
await wrapper.vm.asyncData
})
})
Upvotes: 3
Views: 6779
Reputation: 1724
Context: Vue is initialized client-side. Nuxt is initialized server-side. This means that every lifecycle hook from Nuxt, like asyncData or fetch, will NOT BE TRIGGERED by default with Vue test utils.
Inspiration: https://murani.nl/blog/2021-12-21/how-to-test-nuxtjs-asyncdata-and-fetch-hooks/
UNIT TEST: To test them you have to trigger them yourself. Remember that this hook will just be a normal function, at this point. In order for the test to work, you have to mock every function which asyncData/fetch is triggering. Afterward, you have to check that the mocked functions are called.
Extract the logic from inside the asyncData/Fetch hook into a function. That way it's easier to be tested, and your code is more solid and secure, when you do unit tests.
My file The bellow file it's a mixin which is attached on all of my nuxt pages.
import { mapGetters, mapActions, mapMutations } from "vuex";
import { retrievePageData, retrieveJwt } from "@/utils/client/helpers";
function strapiVue(pageEndPoint, pageName, objProperty) {
return {
computed: {
...mapGetters("jwt", ["getJwt"])
},
methods: {
...mapActions("jwt", ["setJwt"]),
...mapMutations("error", ["SET_ERROR"])
},
async asyncData({ store }) {
const jwtToken = await retrieveJwt(store.state.jwt.jwt);
if (Object.keys(store.state.pages[pageName]).length === 0) {
return retrievePageData(pageEndPoint, jwtToken, objProperty);
}
},
created() {
if (this.error) {
this.SET_ERROR(this.error);
this.$router.push({ name: "error" });
}
},
mounted() {
if (this.getJwt === "") this.setJwt(this.jwt);
}
};
}
export default strapiVue;
My unit test file:
import strapiVue from "@/mixins/strapiVue";
import { shallowMount, createLocalVue } from "@vue/test-utils";
import * as helpers from "@/utils/client/helpers";
import Vuex from "vuex";
import { makeErrorObj } from "@/service/error";
// eslint-disable-next-line no-import-assign
helpers.retrieveJwt = jest.fn(() => "jwt");
// eslint-disable-next-line no-import-assign
helpers.retrievePageData = jest.fn(() => ({
contactPageStrapiData: {},
jwt: "jwt"
}));
const localVue = createLocalVue();
localVue.use(Vuex);
describe("strapiVue", () => {
let modules;
let store;
let mocks;
let wrapperOptions;
let wrapper;
const Component = {
render() {},
mixins: [strapiVue("/contact", "contactPage", "contactPageStrapiData")],
asyncData: strapiVue("/contact", "contactPage", "contactPageStrapiData")
.asyncData
};
beforeEach(() => {
modules = {
jwt: {
state: {
jwt: "jwt"
},
getters: {
getJwt: jest.fn(() => "")
},
actions: {
setJwt: jest.fn()
},
namespaced: true
},
error: {
mutations: {
SET_ERROR: jest.fn()
},
namespaced: true
},
pages: {
state: {
contactPage: []
},
namespaced: true
}
};
store = new Vuex.Store({ modules });
mocks = {
$router: {
push: jest.fn()
}
};
wrapperOptions = {
localVue,
store,
mocks,
data() {
return {
error: makeErrorObj(),
jwt: "jwt"
};
}
};
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe("life cycle hooks", () => {
describe("mounted", () => {
it("should be defined", () => {
wrapper = shallowMount(Component, wrapperOptions);
expect(wrapper).toBeDefined();
});
it("should set the jwt token, if it hasn't been set already", () => {
wrapper = shallowMount(Component, wrapperOptions);
expect(modules.jwt.actions.setJwt).toHaveBeenCalledWith(
expect.any(Object),
"jwt"
);
});
it("should not set the jwt token, if it has been initially set", () => {
modules.jwt.getters.getJwt = jest.fn(() => "mockJwt");
wrapper = shallowMount(Component, {
...wrapperOptions,
store: new Vuex.Store({ modules })
});
expect(modules.jwt.actions.setJwt).not.toHaveBeenCalled();
});
});
describe("created", () => {
describe("an error is present", () => {
it("should call SET_ERROR with a payload", () => {
wrapper = shallowMount(Component, wrapperOptions);
expect(modules.error.mutations.SET_ERROR).toHaveBeenCalledWith(
expect.any(Object),
{
error: {
appCode: "G00000",
message: "This is generic error.",
status: null,
type: "Generic Error"
}
}
);
});
it("should trigger a router push", () => {
wrapper = shallowMount(Component, wrapperOptions);
expect(mocks.$router.push).toHaveBeenCalledWith({
name: "error"
});
});
});
describe("no error", () => {
it("should not call SET_ERROR", () => {
wrapper = shallowMount(Component, {
...wrapperOptions,
data() {
return {};
}
});
expect(modules.error.mutations.SET_ERROR).not.toHaveBeenCalled();
});
it("should trigger a router push", () => {
wrapper = shallowMount(Component, {
...wrapperOptions,
data() {
return {};
}
});
expect(mocks.$router.push).not.toHaveBeenCalled();
});
});
});
describe("asyncData", () => {
const context = {
store: {
state: {
jwt: {
jwt: "jwt"
},
pages: {
contactPage: []
}
}
}
};
it("should call retrieveJwt", async () => {
wrapper = shallowMount(Component, {
...wrapperOptions,
data() {
return {};
}
});
await Component.asyncData(context);
expect(helpers.retrieveJwt).toHaveBeenCalledWith("jwt");
});
it("should call retrievePageData if store module pages is empty", async () => {
wrapper = shallowMount(Component, {
...wrapperOptions,
data() {
return {};
}
});
await Component.asyncData(context);
expect(helpers.retrievePageData).toHaveBeenCalledWith(
"/contact",
"jwt",
"contactPageStrapiData"
);
});
it("should return retrievePageData if store module pages is empty", async () => {
wrapper = shallowMount(Component, {
...wrapperOptions,
data() {
return {};
}
});
const result = await Component.asyncData(context);
expect(result).toMatchObject({ contactPageStrapiData: {}, jwt: "jwt" });
});
it("should not call retrievePageData if store module pages is not empty", async () => {
modules.pages.state.contactPage = [{}];
wrapper = shallowMount(Component, {
...wrapperOptions,
store: new Vuex.Store({ modules }),
data() {
return {};
}
});
await Component.asyncData(context);
expect(helpers.retrievePageData).not.toHaveBeenCalledWith();
});
});
});
});
E2E: In order to test that the hook is called you need to do an end-to-end test. This way you can be 100% sure that the hook is called because the data is presented on the screen.
Upvotes: 1
Reputation: 495
Gyen Abubakar's answer can be a solution if you want to test only the behavior of the component with a mocked data. But keep in mind that the asyncData and fetch hook are not tested, and you may need to test them for a better unit test.
If you want to test your asyncData and fetch hook, you need to add this after mounting the component:
AsyncData
wrapper = shallowMount(Login);
wrapper.setData({
...(await wrapper.vm.$options.asyncData({ store })) // add more context here
});
Fetch Hook
wrapper = shallowMount(Login);
await Login.fetch.call(wrapper.vm); // using `call` to inject `this`
Upvotes: 3
Reputation: 21
Late answer but I hope this helps someone new to this question.
What I did to solve this issue when I faced it, was simply adding the data()
method when mounting (or shallow-mounting) the component:
// import shallowMount & component/page to be tested
const wrapper = shallowMount(Component, {
data() {
// assuming username data is the data needed by component
username: 'john.doe'
}
})
Since asyncData()
runs on the server, our test fails because asyncData()
never runs and hence, the data expected from it is never gotten.
So, it makes sense to provide a client-side data using the data()
method so the component/page has the necessary data at the time the test is ran.
Upvotes: 2