最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

vue.js - Writing test for vue component using pinia store - Stack Overflow

programmeradmin2浏览0评论

I am setting up some tests for a component that is dependent on a pinia store but I'm currently unable to update the store state to affect the component during the tests;

My Component looks like this:

<script setup lang="ts">
import { ComputedRef } from 'vue';
import { storeToRefs } from 'pinia';
import { RouterLink } from 'vue-router';
import { useBooksStore } from '@/stores/books';

interface BookStore {
  pageInfo: PageInfo;
  nextPage: ComputedRef<number>;
  previousPage: ComputedRef<number>;
}

const bookStore = useBooksStore();
const { pageInfo, nextPage, previousPage }: BookStore = storeToRefs(bookStore);
</script>

<template>
  <nav class="book-nav">
    <RouterLink
      class="button"
      data-test-id="previous-page"
      :class="{ 'button--disabled': previousPage < 1 }"
      :to="`/books/${previousPage}`">
      &lt;
    </RouterLink>

    <span>
      Page
      <span data-test-id="page-no">{{ pageInfo.page }}</span>
      of
      <span data-test-id="total-pages">{{ pageInfo.totalPages }}</span>
    </span>

    <RouterLink
      class="button"
      data-test-id="next-page"
      :class="{ 'button--disabled': nextPage > pageInfo.totalPages }"
      :to="`/books/${nextPage}`">
      &gt;
    </RouterLink>
  </nav>
</template>

<style lang="scss" scoped>
.book-nav {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1rem;
}
</style>

My Store looks like this

import { ref, computed } from 'vue';
import { defineStore } from 'pinia';

export const useBooksStore = defineStore('books', () => {

  const pageInfo = ref({
    page: 1,
    totalPages: 5,
  });

  const nextPage = computed<number>(() => pageInfo.value.page + 1);

  const previousPage = computed<number>(() => pageInfo.value.page - 1);

  const updatePage = (page: number) => {
    const clampedPage = Math.min(Math.max(page, 1), pageInfo.value.totalPages);
    pageInfo.value.page = clampedPage;
  };

  return { pageInfo, nextPage, previousPage, updatePage };
});

and this is my test for the BookNav component

import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest';
import { shallowMount, RouterLinkStub, VueWrapper } from '@vue/test-utils';
import { createTestingPinia, type TestingPinia } from '@pinia/testing';
import { setActivePinia, createPinia } from 'pinia';
import { useBooksStore } from '@/stores/books';
import BookNav from './BookNav.vue';

const PREV_PAGE = '[data-test-id=previous-page]';
const NEXT_PAGE = '[data-test-id=next-page]';
const PAGE_NO = '[data-test-id=page-no]';
const TOTAL_PAGES = '[data-test-id=total-pages]';

let wrapper: VueWrapper;
let pinia: TestingPinia;
let store;

describe('shows the current and total pages', () => {
  beforeEach(() => {
    pinia = createTestingPinia({
      createSpy: vi.fn,
      stubActions: false,
    });

    wrapper = shallowMount(BookNav, {
      global: {
        stubs: {
          RouterLink: RouterLinkStub,
        },
        plugins: [pinia],
      },
    });

    store = useBooksStore();
  });

  afterEach(() => {
    wrapper.unmount();
  });

  it('/books shows the current and total pages', () => {
    expect(
      wrapper.find(PREV_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/0');
    expect(wrapper.find(PREV_PAGE).classes()).toContain('button--disabled');

    expect(wrapper.find(PAGE_NO).text()).toContain(1);
    expect(wrapper.find(TOTAL_PAGES).text()).toContain(5);

    expect(
      wrapper.find(NEXT_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/2');
  });

  it('/books/2 updates links and pages on next page', async () => {
    store.updatePage(2);

    expect(
      wrapper.find(PREV_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/1');
    expect(wrapper.find(PREV_PAGE).classes()).not.toContain('button--disabled');

    expect(wrapper.find(PAGE_NO).text()).toContain(2);
    expect(wrapper.find(TOTAL_PAGES).text()).toContain(5);

    expect(
      wrapper.find(NEXT_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/3');
  });
});

The first test is completing correctly as it relies on the default values but the 2nd test is failing because the state isn't being updates from this initial state

 FAIL  src/components/books/BookNav.spec.ts > shows the current and total pages > /books/2 updates links and pages on next page
AssertionError: expected '/books/0' to deeply equal '/books/1'

Do I need to wait for the DOM to update or something?

I am setting up some tests for a component that is dependent on a pinia store but I'm currently unable to update the store state to affect the component during the tests;

My Component looks like this:

<script setup lang="ts">
import { ComputedRef } from 'vue';
import { storeToRefs } from 'pinia';
import { RouterLink } from 'vue-router';
import { useBooksStore } from '@/stores/books';

interface BookStore {
  pageInfo: PageInfo;
  nextPage: ComputedRef<number>;
  previousPage: ComputedRef<number>;
}

const bookStore = useBooksStore();
const { pageInfo, nextPage, previousPage }: BookStore = storeToRefs(bookStore);
</script>

<template>
  <nav class="book-nav">
    <RouterLink
      class="button"
      data-test-id="previous-page"
      :class="{ 'button--disabled': previousPage < 1 }"
      :to="`/books/${previousPage}`">
      &lt;
    </RouterLink>

    <span>
      Page
      <span data-test-id="page-no">{{ pageInfo.page }}</span>
      of
      <span data-test-id="total-pages">{{ pageInfo.totalPages }}</span>
    </span>

    <RouterLink
      class="button"
      data-test-id="next-page"
      :class="{ 'button--disabled': nextPage > pageInfo.totalPages }"
      :to="`/books/${nextPage}`">
      &gt;
    </RouterLink>
  </nav>
</template>

<style lang="scss" scoped>
.book-nav {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1rem;
}
</style>

My Store looks like this

import { ref, computed } from 'vue';
import { defineStore } from 'pinia';

export const useBooksStore = defineStore('books', () => {

  const pageInfo = ref({
    page: 1,
    totalPages: 5,
  });

  const nextPage = computed<number>(() => pageInfo.value.page + 1);

  const previousPage = computed<number>(() => pageInfo.value.page - 1);

  const updatePage = (page: number) => {
    const clampedPage = Math.min(Math.max(page, 1), pageInfo.value.totalPages);
    pageInfo.value.page = clampedPage;
  };

  return { pageInfo, nextPage, previousPage, updatePage };
});

and this is my test for the BookNav component

import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest';
import { shallowMount, RouterLinkStub, VueWrapper } from '@vue/test-utils';
import { createTestingPinia, type TestingPinia } from '@pinia/testing';
import { setActivePinia, createPinia } from 'pinia';
import { useBooksStore } from '@/stores/books';
import BookNav from './BookNav.vue';

const PREV_PAGE = '[data-test-id=previous-page]';
const NEXT_PAGE = '[data-test-id=next-page]';
const PAGE_NO = '[data-test-id=page-no]';
const TOTAL_PAGES = '[data-test-id=total-pages]';

let wrapper: VueWrapper;
let pinia: TestingPinia;
let store;

describe('shows the current and total pages', () => {
  beforeEach(() => {
    pinia = createTestingPinia({
      createSpy: vi.fn,
      stubActions: false,
    });

    wrapper = shallowMount(BookNav, {
      global: {
        stubs: {
          RouterLink: RouterLinkStub,
        },
        plugins: [pinia],
      },
    });

    store = useBooksStore();
  });

  afterEach(() => {
    wrapper.unmount();
  });

  it('/books shows the current and total pages', () => {
    expect(
      wrapper.find(PREV_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/0');
    expect(wrapper.find(PREV_PAGE).classes()).toContain('button--disabled');

    expect(wrapper.find(PAGE_NO).text()).toContain(1);
    expect(wrapper.find(TOTAL_PAGES).text()).toContain(5);

    expect(
      wrapper.find(NEXT_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/2');
  });

  it('/books/2 updates links and pages on next page', async () => {
    store.updatePage(2);

    expect(
      wrapper.find(PREV_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/1');
    expect(wrapper.find(PREV_PAGE).classes()).not.toContain('button--disabled');

    expect(wrapper.find(PAGE_NO).text()).toContain(2);
    expect(wrapper.find(TOTAL_PAGES).text()).toContain(5);

    expect(
      wrapper.find(NEXT_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/3');
  });
});

The first test is completing correctly as it relies on the default values but the 2nd test is failing because the state isn't being updates from this initial state

 FAIL  src/components/books/BookNav.spec.ts > shows the current and total pages > /books/2 updates links and pages on next page
AssertionError: expected '/books/0' to deeply equal '/books/1'

Do I need to wait for the DOM to update or something?

Share Improve this question asked Feb 16 at 22:14 overbyteoverbyte 6489 silver badges25 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

Okay - sounds like I was on the right track.

I imported nextTick() from vue and awaited it before running the tests

import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest';
import { nextTick } from 'vue';
import { shallowMount, RouterLinkStub, VueWrapper } from '@vue/test-utils';
import { createTestingPinia, type TestingPinia } from '@pinia/testing';

import { useBooksStore } from '@/stores/books';
import BookNav from './BookNav.vue';

const PREV_PAGE = '[data-test-id=previous-page]';
const NEXT_PAGE = '[data-test-id=next-page]';
const PAGE_NO = '[data-test-id=page-no]';
const TOTAL_PAGES = '[data-test-id=total-pages]';

let wrapper: VueWrapper;
let pinia: TestingPinia;
let store: ReturnType<typeof useBooksStore>;

describe('shows the current and total pages', () => {
  beforeEach(() => {
    pinia = createTestingPinia({
      createSpy: vi.fn,
      stubActions: false,
    });

    wrapper = shallowMount(BookNav, {
      global: {
        stubs: {
          RouterLink: RouterLinkStub,
        },
        plugins: [pinia],
      },
    });

    store = useBooksStore();
  });

  afterEach(() => {
    wrapper.unmount();
  });

  it('/books shows the current and total pages', () => {
    expect(
      wrapper.find(PREV_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/0');
    expect(wrapper.find(PREV_PAGE).classes()).toContain('button--disabled');

    expect(wrapper.find(PAGE_NO).text()).toContain(1);
    expect(wrapper.find(TOTAL_PAGES).text()).toContain(5);

    expect(
      wrapper.find(NEXT_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/2');
  });

  it('/books/2 updates links and pages on next page', async () => {
    store.updatePage(2);
    // ⚠️ added next tick to wait for vue to update
    await nextTick();

    expect(
      wrapper.find(PREV_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/1');
    expect(wrapper.find(PREV_PAGE).classes()).not.toContain('button--disabled');

    expect(wrapper.find(PAGE_NO).text()).toContain(2);
    expect(wrapper.find(TOTAL_PAGES).text()).toContain(5);

    expect(
      wrapper.find(NEXT_PAGE).findComponent(RouterLinkStub).props('to')
    ).toEqual('/books/3');
  });
});


发布评论

评论列表(0)

  1. 暂无评论