32

Problem

I'm using nuxt 1.4 with routing using Jest to do unit testing. My application doesn't throw errors and seems to work perfectly. However when running my unit test npm run unit (which runs jest) it throws an error in the terminal: [Vue warn]: Unknown custom element: <nuxt-link> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

Expected

I would expect it to not throw this error since my application is working.

Files

package.json:

{
  "name": "vue-starter",
  "version": "1.0.0",
  "description": "Nuxt.js project",
  "private": true,
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
    "precommit": "npm run lint",
    "test": "npm run lint && npm run unit",
    "unit": "jest",
    "unit:report": "jest --coverage"
  },
  "dependencies": {
    "babel-jest": "^22.4.1",
    "jest-serializer-vue": "^1.0.0",
    "node-sass": "^4.7.2",
    "npm": "^5.7.1",
    "nuxt": "^1.0.0",
    "sass-loader": "^6.0.7",
    "vue-jest": "^2.1.1"
  },
  "devDependencies": {
    "@vue/test-utils": "^1.0.0-beta.12",
    "babel-eslint": "^8.2.1",
    "eslint": "^4.15.0",
    "eslint-friendly-formatter": "^3.0.0",
    "eslint-loader": "^1.7.1",
    "eslint-plugin-vue": "^4.0.0",
    "jest": "^22.4.2"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ],
  "jest": {
    "moduleFileExtensions": [
      "js",
      "vue"
    ],
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
      ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
    },
    "snapshotSerializers": [
      "<rootDir>/node_modules/jest-serializer-vue"
    ]
  }
}

The component that I test:

<template>
  <div>
    <nuxt-link class="name" :to="{ path: `entity/${item.id}`, params: { id: item.id }}">{{item.name}}</nuxt-link>
    <button class="connect" @click="connect">{{ btnText }}</button>
  </div>
</template>

<script>
  // import nuxtLink from '../.nuxt/components/nuxt-link';

  const connectionStatusMap = [
    'Connect',
    'Connected',
    'Pending',
    'Cancel',
  ];

  export default {
    /*components: {
      'nuxt-link': nuxtLink,
    },*/
    props: {
      item: {
        type: Object
      }
    },
    ...
  }
</script>

My test script:

import TestItem from '../components/TestItem';
import { shallow, mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import VueRouter from 'vue-router';

const localVue = createLocalVue()

localVue.use(Vuex)
localVue.use(VueRouter)

...
it(`should show the entity`, () => {
    const wrapper = mount(TestItem, {
      propsData: { item },
      localVue,
      store,
      // stubs: ['nuxt-link'],
    })
    expect(wrapper.find('.name').text()).toBe(item.name);
  });

  it(`should show allow me to connect if I'm not yet connected`, () => {
    const wrapper = shallow(TestItem, {
      propsData: { item },
      localVue,
      store,
      stubs: ['nuxt-link'],
    })
    expect(wrapper.find('.connect').text()).toBe('Connect');
  });
  ...

I tried

I tried creating a localVue and also stubbing the component as suggested in this github comment I also tried shallow/mount but that did not seem to work either.

kissu
  • 40,416
  • 14
  • 65
  • 133
Anima-t3d
  • 3,431
  • 6
  • 38
  • 56

9 Answers9

37

This is how I was able to get rid of the annoying warning:

Include RouterLinkStub, eg.:

import { shallowMount, createLocalVue, RouterLinkStub } from '@vue/test-utils';

Map NuxtLink stub to RouterLinkStub

const wrapper = shallowMount(TestItem, {
  ...
  stubs: {
    NuxtLink: RouterLinkStub
  }
})

And in case you were checking nuxt-link text or something, change:

const link = wrapper.find('nuxt-link');

to

const link = wrapper.find(RouterLinkStub);

Found this gold on https://onigra.github.io/blog/2018/03/19/vue-test-utils-router-link-stub/

Good thing you don't need to know japanese to read code...

kissu
  • 40,416
  • 14
  • 65
  • 133
Kristo J
  • 621
  • 6
  • 6
19

Although the answers provided could work. What I ended up using was based on this guide

const wrapper = mount(TestItem, {
  propsData: { item },
  localVue,
  store,
  stubs: {
    NuxtLink: true,
    // Any other component that you want stubbed
  },
});
kissu
  • 40,416
  • 14
  • 65
  • 133
Anima-t3d
  • 3,431
  • 6
  • 38
  • 56
4

I added below lines of code to get this working.

  1. In your test file
import NuxtLink from "path to nuxt-link.js"

Mycomponent.components.NuxtLink = NuxtLink
  1. In your jest conf file
transformIgnorePatterns: [
   "path to nuxt-link.js"
],

Or you could add below line in mount options

mount(Mycomponent, {stubs: ["nuxt-link"]})
kissu
  • 40,416
  • 14
  • 65
  • 133
4

I managed to get it working using this workaround for Storybook:

import { mount, createLocalVue } from '@vue/test-utils'
import Component from '@/components/Component.vue'

const localVue = createLocalVue()

localVue.component('nuxt-link', {
  props:   ['to'],
  template: '<a href="#"><slot>NuxtLink</slot></a>',
})

describe('Test Component', () => {

    const wrapper = mount(Component, {
      stubs: ['nuxt-link'],
      localVue
    })
})

kissu
  • 40,416
  • 14
  • 65
  • 133
mstrlaw
  • 69
  • 3
1
...
import NuxtLink from '../.nuxt/components/nuxt-link.js'

...
TestItem.components = TestItem.components || {};
TestItem.components.NuxtLink = NuxtLink;
const wrapper = shallow(TestItem, {
    ...
});
...
kissu
  • 40,416
  • 14
  • 65
  • 133
xtranophilist
  • 582
  • 8
  • 14
  • Thanks for your reply. I now get this error `console.error node_modules/vue/dist/vue.runtime.common.js:589 [Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option. (found in )` But I don't have any router-link element in my app, only `nuxt-link` – Anima-t3d Apr 18 '18 at 08:45
  • @Anima-t3d did you find a solution to the "router-link" error?. I have the exact same issue. – Oldenborg May 02 '18 at 10:01
  • @Oldenborg I did not yet have the time to try out the solutions. But glad you found yours. – Anima-t3d Jun 21 '18 at 09:47
1

To anyone getting the Unknow custom element: <router-link>

My issue was, I used mount instead of shallow when creating the component.

shallow usage:

Like mount, it creates a Wrapper that contains the mounted and rendered Vue component, but with stubbed child components.

Source: https://vue-test-utils.vuejs.org/en/api/shallow.html

Here is a working example

import { shallow } from '@vue/test-utils';                               
import ContentCard from '../../components/ContentCard.vue';                                                                           
import NuxtLink from '../../.nuxt/components/nuxt-link';                                                                              

const createComponent = propsData => shallow(ContentCard, { propsData });                                                             

describe('ContentCard', () => {                                                                                                       
  let component;                                                                                                                      

  beforeEach(() => {
    ContentCard.components = ContentCard.components || {};                                                                            
    ContentCard.components.NuxtLink = NuxtLink;                                                                                       
  });   

  describe('Properties', () => {
    it('has an imgSrc property', () => {                                                                                              
      component = createComponent({ imgSrc: 'X' });                                                                                   
      expect(component.props().imgSrc).toBe('X');                                                                                     
    });   
  });     
});
kissu
  • 40,416
  • 14
  • 65
  • 133
Oldenborg
  • 906
  • 3
  • 13
  • 26
1

I have:

// path: ./test/jest.setup.js

import Vue from 'vue'
import VueTestUtils from '@vue/test-utils'

// Mock Nuxt components
VueTestUtils.config.stubs['nuxt-link'] = '<a><slot /></a>'
VueTestUtils.config.stubs['no-ssr'] = '<span><slot /></span>'

and

// path: ./jest.config.js

module.exports = {
  // ... other stuff
  setupFilesAfterEnv: ['./test/jest.setup.js']
}

... and this solves all my jest test in the nuxt app

kissu
  • 40,416
  • 14
  • 65
  • 133
Claudiu
  • 3,700
  • 1
  • 38
  • 35
0
// test/jestSetup.js

import Vue from 'vue'
import Vuetify from 'vuetify'
import { config } from '@vue/test-utils'

Vue.use(Vuetify)

config.stubs.NuxtLink = { template: '<a><slot /></a>' }
kissu
  • 40,416
  • 14
  • 65
  • 133
risingagain
  • 210
  • 2
  • 12
0

The accepted answer from Anima-t3d works, but does not account for all use cases.

Please note, that the solutions below are tailored to Nuxt3 with vue test-utils v2, so the mounting options changed. Did not try it out with previous version.

Use case 1:
I don't need to access inner elements of the NuxtLink. => Stubbing is a good option, so this leads to the answert from Anima-t3d:

const wrapper = mount(TestItem, {
  props,
  global: {
    stubs: {
      NuxtLink: true,
    },
  },
});

Note: the stub needs to be defined explicitly for shallowMount as well.

Use case 2:
I want to access inner elements of the NuxtLink for some reasons. => Stubbing won't work, instead we can define a custom component in the test file:

wrapper = mount(TestItem, {
  props,
  global: {
    components: {
      NuxtLink: {
        template: '<a><slot/></a>',
      },
    },
  },
});

Note: if you are using shallowMount, you still need to list NuxtLink in the stubs and set it to false:

wrapper = shallowMount(TestItem, {
  props,
  global: {
    stubs: {
      NuxtLink: false,
    },
    components: {
      NuxtLink: {
        template: '<a><slot/></a>',
      },
    },
  },
});

What this does is replacing the NuxtLink with the template you define for it. Used html elements inside are kept, the attributes (like classes or the "to" attribute) are automatically applied.

This means, given the following usage of NuxtLink

<NuxtLink
  to="www.example.com"
  class="item-class"
><div>ItemContent</div></NuxtLink>

, the output of wrapper.html will then be

<a to="www.example.com" class="item-class"><div>ItemContent</div></a>