I consider unit testing as part of the code itself. Following Angular folder structure, I like having my
.spec.ts
along with the tested files. May you prefer to have atests/
folder at the root folder or a__tests__/
at each folder level, feel free to adapt this tutorial to your taste.As for the mocks, I follow Jest convention by having a
__mocks__/
folder at each folder level
Following dependencies will be used:
Our test runner. As we are using TypeScript, @types/jest
is also added
Jest transformer for our vue components
Vue official unit testing library. Equivalent of Enzyme for React
TypeScript preprocessor for Jest
Required for vue-jest
(StackOverflow link)
yarn add --dev jest @types/jest vue-jest @vue/test-utils ts-jest babel-core@^7.0.0-bridge.0
Add Jest types in tsconfig.json:
{
"compilerOptions": {
"types": ["@types/node", "@nuxt/vue-app", "@types/jest"]
}
}
Add a test
script in package.json:
{
"scripts": {
"dev": "nuxt-ts",
"build": "nuxt-ts build",
"start": "nuxt-ts start",
"generate": "nuxt-ts generate",
"test": "jest"
}
}
Add a Jest configuration file, jest.config.js:
module.exports = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
// this line is optional and the tilde shortcut
// will not be used in this tutorial
'^~/(.*)$': '<rootDir>/$1'
},
transform: {
'^.+\\.ts?$': 'ts-jest',
'.*\\.(vue)$': 'vue-jest'
},
moduleFileExtensions: ['ts', 'js', 'vue', 'json'],
collectCoverageFrom: [
'components/**/*.vue',
'layouts/**/*.vue',
'pages/**/*.vue',
'lib/**/*.ts',
'plugins/**/*.ts',
'store/**/*.ts'
]
};
For more detail: Jest configuration documentation
collectCoverageFrom
: we will be using Jest coverage (which uses Istanbul behind the hoods). We are listing all folders than have to be scanned for coverage so that files which do not have a corresponding.spec.ts
file are flagged as non tested instead of being skipped.
Finally, add a ts-shim.d.ts at root level:
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
If this shim were missing, running tests against Vue components will trigger an
error: error TS2307: Cannot find module '{path to component}'.
. Kudos to
Beetaa for
the solution
Note: lib/polls/ testing is pure TypeScript testing and will be skipped in in this tutorial. For the sake of completion, feel free to check:
You are now ready to run tests with
yarn test
May you are interested into test coverage, please run
yarn test --coverage
and then check coverage/lcov-report/index.html.
As vuex files are plain TypeScript files, it is easier to start there. Let’s create the following files:
Getter is empty so there is nothing to test.
I mocked a state in store/polls/__mocks__/state.mock.ts.
State and mutations testing are pure TypeScript testing and present not much of interest. I then just add the link to the test files:
Actions testing involve API calls. Right, we are not really calling any back-end but let’s imagine we were. Testing should avoid any network call. To fix this, Jest has the manual mock feature.
Following Jest convention, my API mock is located at lib/polls/__mocks__/api.ts:
import { Poll, Vote } from '../models';
export const DUMMY_POLLS: Poll[] = [
// ...
];
export const DUMMY_VOTES: Vote[] = [
// ...
];
export const loadPolls = jest.fn().mockImplementation(
(): Promise<Poll[]> => {
return new Promise<Poll[]>(resolve => resolve(DUMMY_POLLS));
}
);
Two points have to be noticed:
loadPolls
function is exactly identical to the real oneloadPolls
definition is not a function but a jest.fn()
mockWith such mock, we just have to call
// Beware of the star import !!
import * as api from '@/lib/polls/api';
jest.mock('@/lib/polls/api.ts');
at the top of our action testing file. There is no much to add besides the mock point so here is the link of the testing file:
At this stage, API is only returning dummy values and votes are not processed by any back-end (e.g. vote ID has to be generated by a back-end). API structure will evolve, when Axios will enter the scene, and tests will have to be updated accordingly
As Vue components testing relies on Vue Test Utils, please refer to the Vue Test Utils documentation if necessary
PollDetail
and PollList
are tested by:
As Nuxt pages are Vue components, polls page is tested by:
When shallow mounting components, props and methods can be mocked:
const options = {};
const wrapper: Wrapper<PollList> = shallowMount(PollList, options);
Interesting options are:
propsData
to mock props (from PollList.spec.ts):
import { DUMMY_POLLS, DUMMY_VOTES } from '@/lib/polls/__mocks__/api';
const poll: Poll = DUMMY_POLLS[0];
const wrapper: Wrapper<PollList> = shallowMount(PollList, {
propsData: { polls: DUMMY_POLLS, votes: DUMMY_VOTES }
});
methods
to override component methods definition. This could help for
mocking or simply using another implementation (from PollDetail.spec.ts):
import { DUMMY_POLLS } from '@/lib/polls/__mocks__/api';
const poll: Poll = DUMMY_POLLS[0];
const mockedVote: jest.Mock = jest.fn();
const wrapper: Wrapper<PollDetail> = shallowMount(PollDetail, {
propsData: { poll },
methods: { vote: mockedVote }
});
Please check Vue Test Utils mounting options for more details
Because Polls page uses mapped state, Vuex Store has to be mocked as well. Vue Test Utils has some documentation dedicated to testing Vuex in components
import { shallowMount, Wrapper, createLocalVue } from '@vue/test-utils';
import Vuex, { Store } from 'vuex';
import Polls from './polls.vue';
import { RootState } from '@/store/types';
import { mock1 } from '@/store/polls/__mocks__/state.mock';
// Vue config
const localVue = createLocalVue();
localVue.use(Vuex);
// Vuex config
let store: Store<RootState>;
// Component config
let wrapper: Wrapper<Polls>;
const loadPolls: jest.Mock = jest.fn();
store = new Vuex.Store({
modules: {
polls: {
namespaced: true,
actions: { load: loadPolls },
state: mock1()
}
}
});
const wrapper: Wrapper<Polls> = shallowMount(Polls, { localVue, store });
Don’t forget to have
namespaced: true
. All store within store/{some folder} are namespaced by default in Nuxt
Only the required action and state are mocked. There is no need to mock mutations and other actions as they are not used by Polls page.
To generate coverage report, run
yarn test --coverage
and you will have a nice output:
Open coverage/lcov-report/index.html to have a detail HTML report: