Search code examples
javascriptvuejs2jestjs

Unit Testing a Vue.js Router with JEST - How to mock pages?


I am trying to unit test my router routes and, following various examples, I have come up with some test cases. I have split my router out into separate modules for routes, guards and the router itself to make it easier to test, so I'm effectively only testing the route definitions here. Here's a sample of my routes...

import Guards from './guards'
import Views from '@/views'

export default [
    {
        path: '/',
        props: true,
        components: {
            default: Views.Pages.Wizard,
            header: Views.Headers.Wizard
        },
        children: [
            {
                path: '',
                name: 'Welcome',
                component: Views.Welcome
            },
            {
                path: 'verify-guest',
                name: 'ReCaptcha',
                component: Views.ReCaptcha
            }
        ]
    },
    {
        path: '/:postcode/:id/:address',
        props: true,
        components: {
            default: Views.Pages.Main,
            header: Views.Headers.Main
        },
        children: [
            {
                path: '',
                name: 'MyHome',
                props: true,
                component: Views.MyHome,
                beforeEnter: Guards.requireSelectedProperty
            },
            {
                path: 'improvements',
                name: 'Improvements',
                props: true,
                component: Views.Improvements,
                beforeEnter: Guards.requireSelectedProperty
            }
        ]
    }
]

I have written some tests and the tests to load the lading page (welcome) pass but everything else seems to fail that I try - It's as if the router only ever loads the landing page.

The tests look like this...

import Helper from '../../test-helper'
import { mount, createLocalVue } from '@vue/test-utils'

import VueRouter from 'vue-router'

import App from '../../templates/app-bare.vue'
import Views from '@/views'

import routes from '@/router/routes.js'
import MockGuards from '@/router/guards.js'

jest.mock('@/router/guards.js', () => ({
    requireUser: jest.fn(),
    requireFullUser: jest.fn(),
    requireCurrentProject: jest.fn(),
    requireSelectedProperty: jest.fn()
}))

jest.mock('@/views/headers/main.vue', () => ({
    name: 'MainHeader',
    render: h => h('div')
}))

jest.mock('@/views/headers/wizard.vue', () => ({
    name: 'WizardHeader',
    render: h => h('div')
}))

jest.mock('@/views/welcome.vue', () => ({
    name: 'Welcome',
    render: h => h('span')
}))

jest.mock('@/views/my-home.vue', () => ({
    name: 'MyHome',
    render: h => h('section')
}))

const localVue = createLocalVue()
localVue.use(VueRouter)

describe('router', () => {
    let router
    let wrapper

    beforeEach(() => {
        router = new VueRouter({ base: '/', routes })
        wrapper = mount(App, {
            localVue,
            router,
            mocks: Helper.vueMockPlugins()
        })
    })

    describe('can load the landing page', () => {

        it('via a path', () => {
            router.push('/')
            expect(wrapper.find(Views.Welcome).exists()).toBeTruthy()
            expect(wrapper.find(Views.Headers.Wizard).exists()).toBeTruthy()
        })

        it('via a name', () => {
            router.push({ name: 'Welcome' })
            expect(wrapper.find(Views.Welcome).exists()).toBeTruthy()
            expect(wrapper.find(Views.Headers.Wizard).exists()).toBeTruthy()
        })
    })

    /*

    None of these work - can only ever make the router render 'Welcome'

    describe('can load the root of my home', () => {

        it('via a path', () => {
            router.push('/KT18%207LJ/1/3%20The%20Crescent/')
            console.log(wrapper.html())
            expect(wrapper.find(Views.MyHome).exists()).toBeTruthy()
            expect(wrapper.find(Views.Headers.Main).exists()).toBeTruthy()
        })

        it('via a name with parameters', () => {
            router.push({ name: 'MyHome', params: {
                id: 1,
                postcode: 'KT18 7LJ',
                address: '3 The Crescent'
            } })
            console.log(wrapper.html())
            expect(wrapper.find(Views.MyHome).exists()).toBeTruthy()
            expect(wrapper.find(Views.Headers.Main).exists()).toBeTruthy()
        })
    })

    */
})

Any ideas? I'm very new to JEST and unit testing with Vue at all - I've much more prior experience with Jasmine and Mocha / Chai / Sinon.


Solution

  • I fixed this myself in the end. The answer was not with the pages but the mocking of my guards (simple when you see it). Instead of this...

    jest.mock('@/router/guards.js', () => ({
        requireUser: jest.fn(),
        requireFullUser: jest.fn(),
        requireCurrentProject: jest.fn(),
        requireSelectedProperty: jest.fn()
    }))
    

    I needed to replace them with this...

    jest.mock('@/router/guards.js', () => ({
        requireUser: jest.fn((_to, _from, next) => {
            next()
        }),
        requireCurrentProject: jest.fn((_to, _from, next) => {
            next()
        }),
        requireFullUser: jest.fn((_to, _from, next) => {
            next()
        }),
        requireSelectedProperty: jest.fn((_to, _from, next) => {
            next()
        })
    }))
    

    As, without a 'next' function to call the mock guard is hit and then the route doesn't execute beyond that point. The 'next' call is required to actually pass through the rendering of the route.

    'Welcome' appears to render all of the time as it's the landing page and therefore the default return in the event that a guard fails.