Testing vue watchers with vue-testing-library

2.2k views Asked by At

Anyone know how I would test a watcher in a component with vue-testing-library?

Here is my component. I want to test that the method is called when the brand vuex state is updated. With vue test utils it would be easy but I have not found a good way to do this with vue testing library. Has anyone did this before using vue testing library.

<template>
  <v-data-table
    data-testid="builds-table"
    :headers="headers"
    :items="builds"
    :items-per-page="10"
    class="elevation-1"
    :loading="loading"
  >
    <template v-slot:[getItemStatus]="{ item }">
      <v-chip :color="getStatusColor(item.status)" dark>
        {{ item.status }}
      </v-chip>
    </template>
  </v-data-table>
</template>

<script>
import { mapState } from "vuex";
import { getScheduledBuilds } from "../services/buildActivationService";
import { getStatusColor } from "../utils/getStatusColor";

export default {
  name: "BuildsTable",
  data() {
    return {
      loading: false,
      headers: [
        {
          text: "Activation Time",
          align: "start",
          value: "buildActivationTime",
        },
        { text: "Build ID", value: "buildId" },
        { text: "Build Label", value: "buildLabel" },
        { text: "Status", value: "status" },
      ],
      error: "",
    };
  },
  async mounted() {
    this.getBuilds();
  },
  computed: {
    ...mapState(["brand", "builds"]),
    getItemStatus() {
      return `item.status`;
    },
  },
  watch: {
    brand() {
      this.getBuilds();
    },
  },
  methods: {
    getStatusColor(status) {
      return getStatusColor(status);
    },
    async getBuilds() {
      try {
        this.loading = true;
        const builds = await getScheduledBuilds(this.$store.getters.brand);
        this.$store.dispatch("setBuilds", builds);
        this.items = this.$store.getters.builds;
        this.loading = false;
      } catch (error) {
        this.loading = false;
        this.error = error.message;
        this.$store.dispatch("setBuilds", []);
      }
    },
  },
};
</script>
1

There are 1 answers

0
tony19 On

Vue Testing Library is just a wrapper for Vue Test Utils, so the same call verification techniques apply.

Here's how to verify the call with Jest and Vue Testing Library:

  1. Spy on the component method definition before rendering the component:

    import { render } from '@testing-library/vue'
    import BuildsTable from '@/components/BuildsTable.vue'
    
    const getBuilds = jest.spyOn(BuildsTable.methods, 'getBuilds')
    render(BuildsTable)
    
  2. Render the component with a given store and a callback to capture the Vuex store instance under test:

    let store = {
      state: {
        brand: '',
        builds: [],
      }
    }
    const storeCapture = (_, vuexStore) => store = vuexStore
    render(BuildsTable, { store }, storeCapture)
    
  3. Update the store's brand value, and wait a macro tick for the watcher to take effect, then verify the getBuilds spy is called twice (once in mounted() and again in the brand watcher):

    store.state.brand = 'foo'
    await new Promise(r => setTimeout(r)) // wait for effect
    expect(getBuilds).toHaveBeenCalledTimes(2)
    

The full test would look similar to this:

import { render } from '@testing-library/vue'
import BuildsTable from '@/components/BuildsTable.vue'

describe('BuildsTable.vue', () => {
  it('calls getBuilds when brand changes', async() => {
    const getBuilds = jest.spyOn(BuildsTable.methods, 'getBuilds')
    let store = {
      state: {
        brand: '',
        builds: [],
      }
    }
    const storeCapture = (_, vuexStore) => store = vuexStore
    render(BuildsTable, { store }, storeCapture)

    store.state.brand = 'foo'
    await new Promise(r => setTimeout(r)) // wait for effect
    expect(getBuilds).toHaveBeenCalledTimes(2)
  })
})