【问题标题】:Axios catch error Request failed with status code 404Axios 捕获错误请求失败,状态码 404
【发布时间】:2019-08-15 18:46:32
【问题描述】:

我正在测试一个使用 Axios 的登录组件。我尝试使用 axios-mock-adapter 模拟 Axios,但是当我运行测试时,它仍然会出错:

Error: Request failed with status code 404

如何在我的测试中正确地模拟 Axios?

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: 'example@gmail.com',
      password: '111111'
    };

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

    wrapper.vm.email = 'example@gmail.com';
    wrapper.vm.password = '111111';
    wrapper.vm.doSigninNormal()
  })
})

登录.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);
    });
  }
}

【问题讨论】:

  • 是你调用get还是post调用的api,是否存在请检查
  • @AKASHPANDEY 是的,它存在。我想调用函数,然后调用 api(在函数内部)并获得响应。 wrapper.vm.doSigninNormal()
  • 你想在测试中调用真正的API?或者你想提供一个作为 axios 的 mock,并用它来伪造服务器?
  • @Sergeon 我想调用函数。在函数内部调用真正的 api。

标签: unit-testing vue.js jestjs axios axios-mock-adapter


【解决方案1】:

如果 axios 实例适配器(xhr 或 http)被 axios-mock-adapter 接管,则会出现错误 baseURL 配置如下:

{baseURL:'/for/bar'} 

如果我们发送如下请求:

get('/api/v1/exampleService')

最后一个http请求会变成

'http://host:port/for/bar/for/bar/api/v1/exampleService'

因为mock-adapter接管了axios默认的adapter,不符合mock规则的api会通过默认adapter处理,这两个adapter的选择逻辑都经过这里(core/dispatchRequest.js):

if (config.baseURL && !isAbsoluteURL(config.url)) { 
   config.url = combineURLs(config.baseURL, config.url);
}

因此,如果你使用mock,请使用以http://开头的完整url

【讨论】:

    【解决方案2】:

    测试错误的登录 URL

    根本问题是测试代码将axios-mock-adapter设置在与Login.vue中实际使用的不同的URL上,因此请求没有被存根:

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

    解决方法是让测试代码使用相同的 URL(即/login):

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

    需要等待 axios.post()

    单元测试不等待POST 请求,因此测试将无法可靠地验证调用或响应(没有破解)。

    修复方法是更新 doSigninNormal() 以返回 axios.post() 承诺以允许调用者等待结果:

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

    验证登录结果

    要验证结果,您可以声明一个本地数据 prop 来保存登录结果 1️⃣,更新 doSigninNormal() 以处理响应(在测试中使用 fakeData 模拟),捕获结果 2️⃣。然后,在等待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)
    

    【讨论】:

    • 你能给我一个例子来分配道具数据并测试到我的登录spec.js吗?
    【解决方案3】:

    问题出在axios-mock-adapter 包上。它需要使用 .create() 方法的 axios 实例。 看这里: creating an instance

    在您的 App.js 中, 使用:

    import axios from "axios";
    const instance = axios.create();
    
    instance.post("http://localhost/api/user/update", {name: "Test"}, {headers: {"Authorization": "Bearer token")}});
    

    不过,测试中无需更改任何内容。

    我从axios-mock-adapter 的测试中得到了提示。

    这样的一个例子是: post test

    【讨论】:

    • 实际上不需要实例。这恰好是他们的测试设置方式。
    【解决方案4】:

    模拟 Axios:

    有两种简单的方法可以模拟 axios,这样您的测试就不会执行真正的 http 请求,而是使用模拟对象:

     将 axios 设置为组件属性:

    import axios from 'axios`;
    Vue.component({
      data() {
        return {
          axios,
        }
      },
      methods: {
        makeApiCall() {
          return this.axios.post(...)
        }
      }
    })
    

    因此您可以轻松地在测试中注入模拟:

    
    it('test axions', function() {
      const post = jest.fn();
      const mock = {
        post,
      }
      // given 
      const wrapper = shallowMount(myComponent, {
        data: {
          axios: mock,
        }
      });
    
      // when
      wrapper.vm.makeApiCall();
    
      // then
      expect(post).toHaveBeenCalled();
    });
    

    我认为这是最直接的方法。

    使用插件在每个组件中注入 axios

    你可以设置一个像vue-plugin-axios这样的插件来自动将axios注入到每个组件中,比如:

      makeApiCall(){
        this.$axios.post(...)
      }
    

    无需在data 中显式声明。

    然后在您的测试中,不是将模拟作为data 的一部分传递,而是将其作为mocks 的一部分传递,这是vue-test-utils 处理全局注入的方式:

    it('test axions', function() {
      const post = jest.fn();
      const mock = {
        post,
      }
      // given 
      const wrapper = shallowMount(myComponent, {
        mocks: {
          $axios: mock,
        }
      });
    
      // when
      wrapper.vm.makeApiCall();
    
      // then
      expect(post).toHaveBeenCalled();
    });
    

    这是如何模拟axios调用以防止调用真正的axios并执行真正的http请求。

    配置模拟行为和访问调用参数

    使用jest.fn,您可以设置一个模拟函数来返回一个特定的对象,例如:

    const post = jest.fn( () =&gt; ({status: 200, response: ...}) )

    您还可以通过hasBeenCalledWith' method, or more complex stuff via mock.calls` (more info here) 访问调用的参数:

    expect(post).toHaveBeenCalledWith(expectedParams).

    所以,我认为你的最终测试应该如下所示:

    it('calls axios() with endpoint, method and body',async (done) => {
    
      // given
      const formData = { email: 'example@gmail.com', password: '111111' };
      const fakeResponse = {response: "fake response"};
      const email = 'example@gmail.com';
      const uri = 'somepath/login/'; // I dont think you can access Vue process env variables in the tests, so you'll need to hardcode.
      const password = '11111';
    
      const post = jest.fn(() => Promise.resolve({status: 200}) );
    
      const mock = {
        post,
      }
      const wrapper = shallowMount(Component, {
        data() {
          return {
            axios: mock,
            // email,
            // password, // you could do this instead to write to wrapper.vm later
          }
        }
      });
      wrapper.vm.email = 'example@gmail.com';
      wrapper.vm.password = '111111';
    
      // when
      await wrapper.vm.doSigninNormal();
    
      // then
      expect(post).toHaveBeenCalledWith({uri, password, email});
    
      // or
      const calls = post.mock.calls;
      const firstParam = calls[0][0];
      
      expect(firstParam.uri).toBe(uri);
      expect(firstParam.email).toBe(email);
      expect(firstParam.password).toBe(password);
    
      done();
    
    });
    

    【讨论】:

    • 我努力了,结果像这样请看image
    猜你喜欢
    • 1970-01-01
    • 2021-04-10
    • 2020-03-29
    • 2020-09-18
    • 2019-10-12
    • 1970-01-01
    • 2022-11-13
    • 2020-04-05
    • 2020-07-30
    相关资源
    最近更新 更多