Oscar Schafer
Oscar Schafer

Reputation: 1545

Vue component fails to render when using mocked fetch response in unit test

I want to test that a BootstrapVue table is rendered correctly based on the data provided by an API. As part of this, I've mocked the fetch function. When I provide the data statically using the loadItems() method, it works fine - 3 rows are rendered. But when I instead swap it to using the API calling method getData() - the rows aren't rendered.

The mocking of fetch appears to work as the API response is logged successfully, but it doesn't seem to make it to the data option of the Vue component.

The Vue component to test:

// MenuTable.vue
<template>
  <div>
    <b-table class="table"
    :items="items" :fields="fields">
    </b-table>
  </div>
</template>

<script>
export default {
  methods: {
    getData() {
      fetch('https://www.myexampleapi.com/menu', {
        method: 'GET',
        mode: 'cors',
      })
        .then((response) => response.json())
        .then((data) => {
          console.log(data);
          this.items = data;
        })
        .catch((error) => {
          this.error = error.message;
          console.log(error);
        });
    },
    loadItems() {
      return [
        { cost: 10, food: 'Spaghetti' },
        { cost: 20, food: 'Pizza' },
        { cost: 30, food: 'Hamburger' },
      ];
    },
  },
  created() {
    this.items = this.loadItems(); // load items statically
    this.getData(); // load items from API
  },
  data() {
    return {
      fields: ['food', 'cost'],
      items: [],
    };
  },
};
</script>

The test file:

// MenuTable.spec.js
import { mount, createLocalVue } from '@vue/test-utils';
import MenuTable from '@/components/MenuTable.vue';

// eslint-disable-next-line no-unused-vars
import { BootstrapVue, BButton, BTable, BRow } from 'bootstrap-vue';

// create an extended `Vue` constructor
const localVue = createLocalVue();

// install plugins as normal
localVue.use(BootstrapVue);

describe('Table.vue Buttons Test', () => {
  let wrapper = null;

  // SETUP - run before to each unit test
  beforeEach(() => {
    global.fetch = jest.fn(() => Promise.resolve({
      json: () => Promise.resolve([
        { cost: 10, food: 'Spaghetti' },
        { cost: 20, food: 'Pizza' },
        { cost: 30, food: 'Hamburger' },
      ]),
    }));

    // render the component
    wrapper = mount(MinimalTable, {
      localVue,
    });
  });

  // TEARDOWN - run after to each unit test
  afterEach(() => {
    jest.resetModules();
    jest.clearAllMocks();
    jest.restoreAllMocks();
    wrapper.destroy();
  });

  it('renders three rows', () => {
    const tableComponent = wrapper.findAll('tbody > tr');
    console.log(tableComponent.length);
    expect(tableComponent.length).toEqual(3);
  });
});

I've made sure it's not a case of shallow mounting by using mount instead, and I think I've made sure that the test awaits a response from the API response by using Promises.

How come the API response data can be logged, but not assigned to the items data variable?

Upvotes: 1

Views: 793

Answers (1)

tony19
tony19

Reputation: 138696

It seems you're expecting Jest to automatically await the Promises, but you actually have to do that explicitly. You can wait until the next macro tick when the fetch's Promises resolve by awaiting a setTimeout() without a specified delay:

it('renders three rows', async () => {
  // wait macro tick when `fetch` Promises resolve
  await new Promise(r => setTimeout(r))

  const tableComponent = wrapper.findAll('tbody > tr');
  console.log(tableComponent.length);
  expect(tableComponent.length).toEqual(3);
})

Upvotes: 2

Related Questions