Skip to content
This repository has been archived by the owner on Jan 12, 2023. It is now read-only.

Latest commit

 

History

History
318 lines (228 loc) · 8.83 KB

06.test.md

File metadata and controls

318 lines (228 loc) · 8.83 KB

Testing

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 a tests/ 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

Adding and configuring Jest

Following dependencies will be used:

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.

Vuex testing

As vuex files are plain TypeScript files, it is easier to start there. Let's create the following files:

  • /store/polls/state.spec.ts
  • /store/polls/mutations.spec.ts
  • /store/polls/actions.spec.ts

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:

  • The loadPolls function is exactly identical to the real one
  • loadPolls definition is not a function but a jest.fn() mock

With 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

Components testing

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:

Shallow mounting

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

Mocking Vuex store

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.

Coverage

To generate coverage report, run

yarn test --coverage

and you will have a nice output:

console coverage

Open coverage/lcov-report/index.html to have a detail HTML report:

HTML coverage