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.