Search code examples
unit-testingvue.jsjestjsaxiosaxios-mock-adapter

Axios catch error Request failed with status code 404


I'm testing a login component that uses Axios. I tried mocking Axios with axios-mock-adapter, but when I run the tests, it still errors out with:

Error: Request failed with status code 404

How do I properly mock Axios in my tests?

login.spec.js:

import Vue from 'vue'
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Login from '../../src/components/global/login/Login.vue';
import Raven from "raven-js";
import jQuery from 'jquery'
import Vuex from 'vuex'
import router from '../../src/router'
var axios = require('axios');
var MockAdapter = require('axios-mock-adapter');

describe('Login.vue', () => {
  let wrapper;
  let componentInstance;
  let mock;
  beforeEach(() => {
    global.requestAnimationFrame = setImmediate,
    mock = new MockAdapter(axios)
    wrapper = shallowMount(Login, {
      router,
      $: jQuery,
      attachToDocument: true,
      mocks: {
        $t: () => { },
        Raven: Raven,
      },
      data() {
        return {
          email: '',
          password: '',
        }
      }
    })
    componentInstance = wrapper.vm;
  })

  afterEach(() => {
    mock.reset()
  })

  it('calls `axios()` with `endpoint`, `method` and `body`', async () => {
    const formData = {
      email: '[email protected]',
      password: '111111'
    };

    let fakeData = { data: "fake response" }
    mock.onPost(`${process.env.VUE_APP_BASE_URL}/login/`, formData).reply(200, fakeData);

    wrapper.vm.email = '[email protected]';
    wrapper.vm.password = '111111';
    wrapper.vm.doSigninNormal()
  })
})

Login.vue

doSigninNormal() {
  const formData = {
    email: this.email,
    password: this.password
  };
  this.$v.$touch()
  if (this.$v.$invalid ) {
    this.loading = false;
    this.emailLostFocus = true;
    this.passwordLostFocus = true;
    $('html, body').animate({scrollTop:110}, 'slow')

  } else {
    axios.post("/login", formData, {
      headers: { "X-localization": localStorage.getItem("lan") }
    })
    .then(res => {
      if (!res.data.result) {
        if (res.data.errors) {
          for (var i = 0; i < res.data.errors.length; i++) {
            this.$toaster.error(res.data.errors[i].message);
            if (
              res.data.errors[0].message == "Your email is not yet verified"
            ) {
              this.showVerificationLinkButton = true;
            }
            if (res.data.errors[i].field === "email") {
              this.$toaster.error(res.data.errors[i].message);
            }
            if (res.data.errors[i].field === "password") {
              this.$toaster.error(res.data.errors[i].message);
            }
          }
        }

        this.loading = false;
        this.$v.$reset();
      } else {
        this.loading = false;
        Raven.setUserContext({
          email: res.data.user.email,
          id: res.data.user.id
        });
        this.$store.dispatch("login", res);
        this.$v.$reset();
      }
    })
    .catch((err) => {
       console.log('catch', err);
    });
  }
}

Solution

  • Testing wrong login URL

    The root problem is the test code sets up axios-mock-adapter on a different URL than actually used in Login.vue, so the request is not stubbed:

    // login.spec.js:
    mock.onPost(`${process.env.VUE_APP_BASE_URL}/login/`, formData).reply(200, fakeData)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    // Login.vue
    axios.post("/login", formData)
                ^^^^^^
    

    The fix is to make the test code use the same URL (i.e., /login):

    // login.spec.js
    mock.onPost("/login", formData).reply(200, fakeData)
    

    Need to await axios.post()

    The unit test isn't awaiting the POST request, so the test wouldn't be able to reliably verify calls or responses (without a hack).

    The fix is to update doSigninNormal() to return the axios.post() promise to allow callers to await the result:

    // Login.vue
    doSigninNormal() {
      return axios.post(...)
    }
    
    // login.spec.js
    await wrapper.vm.doSigninNormal()
    expect(mock.history.post.length).toBe(1)
    

    Verifying login result

    To verify the result, you could declare a local data prop to hold the login result 1️⃣, update doSigninNormal() to process the response (which is mocked with fakeData in the test), capturing the result 2️⃣. Then, just check that data property after awaiting doSignInNormal().

    // Login.vue
    data() {
      return {
        ...
        result: '' 1️⃣
      }
    }
    methods: {
      doSignInNormal() {
        return axios.post(...)
                .then(resp => this.result = resp.data.result) 2️⃣
      }
    }
    
    // login.spec.js
    const result = await wrapper.vm.doSigninNormal()
    expect(result).toBe(fakeData.result)
    expect(wrapper.vm.result).toBe(fakeData.result)
    

    Edit Mocking Axios calls with axios-mock-adapter