1

I have simple Vue component that fetches API key when it is created and key can be renewed by clicking on button:

<template>
    <div>
        <div>{{data.api_key}}</div>
        <button ref="refresh-trigger" @click="refreshKey()">refresh</button>
    </div>
</template>
<script>
    export default {
        created() {
            axios.get(this.url).then((response) => {
                this.data = response.data
            })
        }
        methods: {
            refreshKey() {
                axios.put(this.url).then((response) => {
                    this.data = response.data
                })
            },
        }
    }
</script>

And I want to test it with this code:

import {shallowMount} from '@vue/test-utils';
import axios from 'axios';
import apiPage from '../apiPage';
import MockAdapter from 'axios-mock-adapter';



describe('API page', () => {
    it('should renew API key it on refresh', async (done) => {
        const flushPromises = () => new Promise(resolve => setTimeout(resolve))
        const initialData = {
            api_key: 'initial_API_key',
        };
        const newData = {
            api_key: 'new_API_key',
        };
        const mockAxios = new MockAdapter(axios);

        mockAxios.onGet('/someurl.json').replyOnce(200, initialData)
        mockAxios.onPut('/someurl.json').replyOnce(200, newData);

        const wrapper = shallowMount(api);
        expect(wrapper.vm.$data.data.api_key).toBeFalsy();

        await flushPromises()

        wrapper.vm.$nextTick(() => {
            expect(wrapper.vm.$data.data.api_key).toEqual(initialData.api_key);
            done()
        });

        wrapper.find({ref: 'refresh-trigger'}).trigger('click');

        wrapper.vm.$nextTick(() => {
            console.log(mockAxios.history)

            expect(wrapper.vm.$data.data.api_key).toEqual(newData.api_key);

            expect(mockAxios.history.get.length).toBe(1);
            expect(mockAxios.history.get[1].data).toBe(JSON.stringify(initialData));

            expect(mockAxios.history.put.length).toBe(1);
            done();
        });
    })
});

But it turns out only get request is mocked because i receive:

        [Vue warn]: Error in nextTick: "Error: expect(received).toEqual(expected)

Difference:

    - Expected
    + Received

    - new_API_key
    + initial_API_key"

found in

---> <Anonymous>
<Root>

console.error node_modules/vue/dist/vue.runtime.common.dev.js:1883
{ Error: expect(received).toEqual(expected)

Even worse, console.log(mockAxios.history) returns empty put array:

{ get:
   [ { transformRequest: [Object],
       transformResponse: [Object],
       timeout: 0,
       xsrfCookieName: 'XSRF-TOKEN',
       xsrfHeaderName: 'X-XSRF-TOKEN',
       maxContentLength: -1,
       validateStatus: [Function: validateStatus],
       headers: [Object],
       method: 'get',
       url: '/admin/options/api.json',
       data: undefined } ],
  post: [],
  head: [],
  delete: [],
  patch: [],
  put: [],
  options: [],
  list: [] }

I tried to define mockAxios in describe block, and console.log it after iteration - and it turns out that put request was here. But not when I needed it. :)

What am i doing wrong? Maybe there are some ways to check if created callback was called and all async functions inside it are done? Maybe i'm using axios-mock wrong?

crcerror
  • 119
  • 3
  • 16

1 Answers1

0

This test code should pass:

import {shallowMount, createLocalVue} from '@vue/test-utils';
import axios from 'axios';
import api from '@/components/api.vue';
import MockAdapter from 'axios-mock-adapter';
describe('API page', () => {
    it('should renew API key it on refresh', async () => {
        const flushPromises = () => new Promise(resolve => setTimeout(resolve))
        const initialData = {
            api_key: 'initial_API_key',
        };
        const newData = {
            api_key: 'new_API_key',
        };
        const mockAxios = new MockAdapter(axios);
        const localVue = createLocalVue();
        mockAxios
          .onGet('/someurl.json').reply(200, initialData)
          .onPut('/someurl.json').reply(200, newData);

        const wrapper = shallowMount(api, {
          localVue,
        });
        expect(wrapper.vm.$data.data.api_key).toBeFalsy();

        await flushPromises();
        expect(wrapper.vm.$data.data.api_key).toEqual(initialData.api_key);
        wrapper.find({ref: 'refresh-trigger'}).trigger('click');
        await flushPromises();
        console.log(mockAxios.history);
        expect(wrapper.vm.$data.data.api_key).toEqual(newData.api_key);

        expect(mockAxios.history.get.length).toBe(1);
        expect(mockAxios.history.put.length).toBe(1);

    })
});

A few notes:

  1. I prefer to chain the responses on the mockAxios object, that way you can group them by URL so it's clear which endpoint you're mocking:
mockAxios
  .onGet('/someurl.json').reply(200, initialData)
  .onPut('/someurl.json').reply(200, newData);

mockAxios
  .onGet('/anotherUrl.json').reply(200, initialData)
  .onPut('/anotherUrl.json').reply(200, newData);
  1. If you want to test that you only made one GET call to the endpoint (with expect(......get.length).toBe(1)) then you should really use reply() instead of replyOnce() and test it the way you're doing it already. The replyOnce() function will remove the handler after replying first time and you'll be getting 404s in your subsequent requests.

  2. mockAxios.history.get[1].data will not contain anything for 3 reasons: GET requests don't have a body (only URL parameters), you only made 1 GET request (here you're checking 2nd GET), and this statement refers to the request that was sent, not data you received.

  3. You're using async/await feature, which means you can take advantage of that for $nextTick: await wrapper.vm.$nextTick(); and drop the done() call all together, but since you already have flushPromises() you might as well use that.

  4. You don't need to test that you received initialData in the 1st call with this line: expect(mockAxios.history.get[1].data).toBe(JSON.stringify(initialData)); since you're already testing it with expect(...).toEqual(apiKey).

  5. Use createLocalVue() utility to create a local instance of Vue for each mount to avoid contaminating the global Vue instance (useful if you have multiple test groups)

and finally, 7. it's best to break this test up into multiple it statements; unit tests should be microtests, i.e. test a small, clearly identifiable behaviour. Although I didn't break the test up for you so it contains as little changes as possible, I'd highly recommend doing it.

ierdna
  • 5,753
  • 7
  • 50
  • 84