Jest-unit-test--- jest axios api call resolves instantly and loading state doesn't activate?

I’m doing a jest unit test in one of my components in my vue-cli-3 project.
In my test I’m checking that the loader class exists when the component renders but after the mocked axios api call resolves it should not exist anymore.

My mocked axios module seems to be working fine, but I’m getting an error showing that the loader class does not exist, I suspect it might have something to do with me not using setTimeout or nextTick in the axios module or maybe my axios call resolves instantly as the function runs in the created hook of the component? but i thought thats what flush promises are for?

Update: So far after running a couple of different tests, it seems that my api call resolves instantly, which is of course what is desired in the application so is there a different kind of test i should be carrying out instead of the one that is currently failing?

Here is my mocked axios module…

export default {
  get: jest.fn(() =>
    Promise.resolve({
      data: [
        {
          name: 'AB:06',
          description: 'This is the most amazing craft beer in the universe.',
          image_url: 'https://images.punkapi.com/v2/17.png',
          brewers_tips: 'Boil down to desired OG (about 17 litres).',
          ingredients: {
            hops: [{ name: 'Saaz' }, { name: 'Amarillo' }, { name: 'Saaz' }],
            malt: [
              { name: 'Munich' },
              { name: 'Wheat' },
              { name: 'Caramalt' },
              { name: 'Amber' },
              { name: 'Munich' }
            ],
            yeast: 'Saflager S189'
          }
        }
      ]
    })
  )
}

Here is my test…

import { mount } from '@vue/test-utils'
import Beer from '@/views/Beer'
import flushPromises from 'flush-promises'
import axios from 'axios'

jest.mock('axios', () => require('../__mocks__/axios.mock.js'))

describe('Beer.vue', () => {
  let wrapper
  beforeEach(() => {
    wrapper = mount(Beer, {
      propsData: {
        id: '9'
      }
    })
    jest.resetModules()
    jest.clearAllMocks()
  })

  it('renders without errors!', () => {
    expect(wrapper.isVueInstance()).toBeTruthy()
  })

  it('shows loader initially and hides it when beer is fetched', async () => {
    expect(wrapper.contains('.loader')).toBe(true)
    await flushPromises()
    expect(wrapper.contains('.loader')).toBe(false)
  })
})

Here is the component i’m testing…

<template>
  <div>
    <div v-if="loading" class="loader">
      <img src="@/assets/loader.svg" class="loader-icon" />
    </div>
    <div v-if="!loading" class="beer-container">
      <div class="description">
        <h3 data-aos="fade-right" data-aos-delay="500">{{ beer.description }}</h3>
      </div>
      <div class="img-name">
        <h1 data-aos="fade-down" data-aos-delay="500">{{ beer.name }}</h1>
        <div data-aos="fade-left" data-aos-delay="500" class="imageUrl">
          <img :src="beer.image_url" alt />
        </div>
      </div>
      <div class="ingredients">
        <h1 data-aos="fade-down">Ingredients</h1>
        <div data-aos="fade-right" class="hops">
          <h3>Hops</h3>
          <ul>
            <li v-for="(hop, index) in removeDuplicateHop" :key="index">{{ hop }}</li>
          </ul>
        </div>
        <div data-aos="fade-left" class="malt">
          <h3>Malt</h3>
          <ul>
            <li v-for="(malt, index) in removeDuplicateMalt" :key="index">{{ malt }}</li>
          </ul>
        </div>
        <div data-aos="fade-right" data-aos-offset="30" class="yeast">
          <h3>Yeast</h3>
          <h4>{{ beer.ingredients.yeast }}</h4>
        </div>
      </div>
      <div class="brewer-tips">
        <h2 data-aos="fade-up">Brewer's Tips</h2>
        <h3 data-aos="fade-down">{{ beer.brewers_tips }}</h3>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: "Beer",
  data() {
    return {
      beer: {},
      loading: true
    };
  },
  props: {
    id: {
      type: String,
      required: true
    }
  },
  created() {
    this.getBeer();
  },
  methods: {
    async getBeer() {
      try {
        const response = await axios.get(this.apiUrl);

        this.beer = response.data[0];
        this.loading = false;
      } catch (error) {
        console.log(error);
      }
    }
  },
  computed: {
    apiUrl() {
      return `https://api.punkapi.com/v2/beers/${this.id}`;
    },
    removeDuplicateHop() {
      if (!this.loading) {
        return [...new Set(this.beer.ingredients.hops.map(({ name }) => name))];
      }
    },
    removeDuplicateMalt() {
      if (!this.loading) {
        return [...new Set(this.beer.ingredients.malt.map(({ name }) => name))];
      }
    }
  }
};
</script>

I’m receiving this error

Expected: true
    Received: false

      23 | 
      24 |   it('shows loader initially and hides it when beer is fetched', async () => {
    > 25 |     expect(wrapper.contains('.loader')).toBe(true)
         |                                         ^
      26 |     await flushPromises()
      27 |     expect(wrapper.contains('.loader')).toBe(false)
      28 |   })

Thanks guys, if you need more information please ask. I tried to provide as much as i can.

1 Like

Promise.resolve() will be resolved in the call stack’s microtask queue before the re-render takes place.

You could use Vue.nextTick() to move the resolution until after the re-render has happened:

import Vue from 'vue'
export default {
  get: jest.fn(() => {
    Vue.nextTick().then(() => ({
      data: [

    // ....

Vue.nextTick() will return a Promise that resolves on the next tick, which means after updates have been processed.

Ah, can’t believe didnt realize that. Thanks Linus

I implemented what you suggested but now I’m getting the opposite error

export default {
  get: jest.fn(() => {
    Vue.nextTick().then(() => ({
      data: [
        {
          name: 'AB:06',
          description: 'This is the most amazing craft beer in the universe.',
          image_url: 'https://images.punkapi.com/v2/17.png',
          brewers_tips: 'Boil down to desired OG (about 17 litres).',
          ingredients: {
            hops: [{ name: 'Saaz' }, { name: 'Amarillo' }, { name: 'Saaz' }],
            malt: [
              { name: 'Munich' },
              { name: 'Wheat' },
              { name: 'Caramalt' },
              { name: 'Amber' },
              { name: 'Munich' }
            ],
            yeast: 'Saflager S189'
          }
        }
      ]
    }))
  })
}
Expected: false
    Received: true

      28 |     expect(wrapper.contains('.loader')).toBe(true)
      29 |     await flushPromises()
    > 30 |     expect(wrapper.contains('.loader')).toBe(false)
         |                                         ^
      31 |   })

well, the promise is flushed but updates are still done on the next tick, so:

expect(wrapper.contains('.loader')).toBe(true)
await flushPromises()
await Vue.nextTick()
expect(wrapper.contains('.loader')).toBe(false)

usually does the trick in these kinds of situations.

1 Like