3
votes

Long time user of the wisdom of StackOverflow on and off the job, but first time I'm posting a question. What a milestone!

I'm writing unit tests for a large Vue application, and one of my components uses a method that references $route in order to determine if a query param is being passed in / pass in the param if it is being used. The method calling this.$route.query.article_id works great, however now that I am in testing, the tests don't recognize this.$route.query

I've tried to mock the $route object when using shallowMount to mount my localVue, as described in the doc, but it doesn't work, and I continue to get the same error.

Here is my component:

<template>
  <b-container fluid>
    <div class="content-page-header"></div>
    <b-row>
      <b-col cols="3" class="outer-columns text-center" style="color:grey">
        <font-awesome-icon
          :icon="['fas', 'newspaper']"
          class="fa-9x content-page-photo mb-3 circle-icon"
        />
        <br />
        <br />Get practical tips and helpful
        <br />advice in clear articles written
        <br />by our staff's experts.
      </b-col>
      <b-col cols="6" v-if="articlesExist">
        <h1 class="header-text">
          <b>Articles</b>
        </h1>
        <div v-if="!selectedArticle">
          <div v-for="article in articles">
            <article-card :article="article" @clicked="onClickRead" />
            <br />
          </div>
        </div>
        <div v-else>
          <router-link to="articles" v-on:click.native="setSelectedArticle(null)">
            <font-awesome-icon icon="chevron-circle-left" />&nbsp
            <b>Back to All Articles</b>
          </router-link>
          <article-header :article="selectedArticle" />
          <br />
          <span v-html="selectedArticle.text"></span>
        </div>
      </b-col>
      <b-col cols="6" v-else>
        <h1 class="header-text">
          <b>Articles</b>
        </h1>
        <div class="text-center">Stay tuned for more Articles</div>
      </b-col>
      <b-col class="outer-columns">
        <b class="text-color" style="font-size:14pt">Saved Articles</b>
        <div v-for="article in userArticles">
          <router-link
            :to="{path:'articles', query: {article_id: article.article.id}}"
            v-on:click.native="setSelectedArticle(article.article)"
          >
            <user-article :article="article.article" />
          </router-link>
          <br />
        </div>
      </b-col>
    </b-row>
  </b-container>
</template>

<script>
import ArticleCard from "./ArticleCard";
import UserArticle from "./UserArticle";
import ArticleHeader from "./ArticleHeader";
import { library } from "@fortawesome/fontawesome-svg-core";
import {
  faNewspaper,
  faChevronCircleLeft
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";

library.add(faNewspaper, faChevronCircleLeft);

export default {
  name: "Articles",

  props: [],

  components: {
    ArticleCard,
    ArticleHeader,
    UserArticle,
    library,
    FontAwesomeIcon,
    faNewspaper,
    faChevronCircleLeft
  },

  mixins: [],

  data() {
    return {
      selectedArticle: null
    };
  },

  computed: {
    articles() {
      return this.$store.getters.articles.filter(article => article.text);
    },
    articlesExist() {
      return Array.isArray(this.articles) && this.articles.length;
    },
    userArticles() {
      return this.$store.getters.userArticles;
    },
    articleParam() {
      return parseInt(this.$route.query.article_id);
    }
  },

  methods: {
    setSelectedArticle(article) {
      this.selectedArticle = article;
    },
    onClickRead(article) {
      this.selectedArticle = article;
    }
  },

  mounted() {
    if (this.articleParam) {
      this.setSelectedArticle(
        this.articles.filter(article => article.id === this.articleParam)[0]
      );
    }
  }
};
</script>

<style lang="stylus" scoped>
.text-color {
  color: #549DB0;
}

.header-text {
  color: white;
  margin-top: -50px;
  margin-bottom: 20px;
}

.outer-columns {
  background-color: #F2FBFD;
  padding-top: 20px;
}

.nav-back {
  color: #549DB0;
  background-color: #F0FBFD;
  padding: 5px;
}
</style>

And here is my test:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
import Articles from '../../../app/javascript/components/member-dashboard/Articles.vue'

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

describe('Articles', () => {
    let store
    let getters
    let state = {
        articles: [
            {
                title: "Testing Vue Components"                
            },
            {
                title: "This One shows",
                text: "<p>You can see me!</p>"
            },
            {
                title: "Another One",
                text: "<p>See me too!</p>"
            }
        ],
        userArticles: [
            {article: {
                title: "This One shows",
                text: "<p>You can see me!</p>"
            }},
            {article: {
                title: "Another One",
                text: "<p>See me too!</p>"
            }}
        ]
    }

    beforeEach(() => {
        getters = {
            articles: () => {
                return state.articles
            },
            userArticles: () => {
                return state.userArticles
            }
        }

        store = new Vuex.Store({ getters })
    })

    it('only displays article with body text', () => {
        const wrapper = shallowMount(Articles, {
            store,
            localVue
        })

        expect(wrapper.vm.articles.length).to.deep.equal(2)
    })
})

As I mentioned, in the shallow mount, I've tried doing this:

 const wrapper = shallowMount(Articles, {
            store,
            localVue,
            mocks: {
                $route: {
                    query: null
                }
            }
        })

But I continue to get this error:

TypeError: Cannot read property 'query' of undefined
      at VueComponent.articleParam (webpack-internal:///1:107:35)

When I remove the line return parseInt(this.$route.query.article_id); from the articleParam method, my test passes.

How do I get around this call to this.$route.query in the component? It's not necessary to my test, but is causing my test to fail when mounting the component.

1

1 Answers

7
votes

import import VueRouter from 'vue-router'; in your unite test file and create a new object of the router like const router = new VueRouter(); and use it in your test case.

I have updated code here:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
import Articles from '../../../app/javascript/components/member-dashboard/Articles.vue'

const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)
localVue.use(BootstrapVue);
const router = new VueRouter();

describe('Articles', () => {
    let store
    let getters
    let state = {
        articles: [
            {
                title: "Testing Vue Components"                
            },
            {
                title: "This One shows",
                text: "<p>You can see me!</p>"
            },
            {
                title: "Another One",
                text: "<p>See me too!</p>"
            }
        ],
        userArticles: [
            {article: {
                title: "This One shows",
                text: "<p>You can see me!</p>"
            }},
            {article: {
                title: "Another One",
                text: "<p>See me too!</p>"
            }}
        ]
    }

    beforeEach(() => {
        getters = {
            articles: () => {
                return state.articles
            },
            userArticles: () => {
                return state.userArticles
            }
        }

        store = new Vuex.Store({ getters })
    })

    it('only displays article with body text', () => {
        const wrapper = shallowMount(Articles, {
            store,
            router,
            localVue
        })

        expect(wrapper.vm.articles.length).to.deep.equal(2)
    })
})