ctimmerm / axios-mock-adapter

Axios adapter that allows to easily mock requests
MIT License
3.42k stars 241 forks source link

Mock failed with 404 Error #116

Open kirankbs opened 6 years ago

kirankbs commented 6 years ago

i am using axios-mock-adapter to mock tests in react app. got below error while running single test but it is passing in case of all tests.

/home/dev/code/client/node_modules/react-scripts/scripts/test.js:22 throw err; ^ Error: Request failed with status code 404 at createErrorResponse (/home/dev/code/client/node_modules/axios-mock-adapter/src/utils.js:122:15) at Object.settle (/home/dev/code/client/node_modules/axios-mock-adapter/src/utils.js:102:16) at handleRequest (/home/dev/code/client/node_modules/axios-mock-adapter/src/handle_request.js:69:11) at /home/dev/code/client/node_modules/axios-mock-adapter/src/index.js:16:9 at MockAdapter. (/home/dev/code/client/node_modules/axios-mock-adapter/src/index.js:15:14) at dispatchRequest (/home/dev/code/client/node_modules/axios/lib/core/dispatchRequest.js:52:10)

ctimmerm commented 6 years ago

I can't do much with the little information that you're giving, but likely the code that adds the mock handlers does not get run when running a single test.

HawiCaesar commented 6 years ago

I got a similar error to @kirankbs.

My code seems to not evaluate the axios.get bit with mock

Here is a sample of my code

export const getLocationInfo = (lat, lng) => {
  return ((dispatch) => {
    return axios.get(process.env.API_LOCATION_URL+`/json?latlng=${lat},${lng}&key=${process.env.GOOGLE_KEY}`)
      .then((response) => {
        dispatch({ type: "FETCHED_LOCATION_INFO", payload: response.data.results });
      }).catch((error) => {
        dispatch({ type: 'FAILED_FETCHING_LOCATION_INFO', payload: error });
      });
  });
};

The test code below


it('should dispatch FETCHED_LOCATION_INFO when getLocationInfo completes successfully', (done) => {

    let middlewares = [thunk];
    let mockStore = configureMockStore(middlewares);
    let store = mockStore({});
    mock = new MockAdapter(axios);
    mock.onGet('http://maps.google.com/').reply(200, {
      data: {
        results: [
          {
            formatted_address: 'Abc Rd, City, Country'
          }
        ]
      }
    });

    store.dispatch(getLocationInfo(-5.2177265, 12.9889)).then(() => {
       const actualActions = store.getActions();
       expect(expectedActions[0].type).toEqual(actualActions[0].type);
       done();
    });
  });

This is my error

[ { type: 'FAILED_FETCHING_LOCATION_INFO',
    payload: { Error: Request failed with status code 404
    at createErrorResponse (/Users/brianhawi/Documents/learning-projects/weather-app/node_modules/axios-mock-adapter/src/utils.js:117:15)
    at Object.settle (/Users/brianhawi/Documents/learning-projects/weather-app/node_modules/axios-mock-adapter/src/utils.js:97:16)
    at handleRequest (/Users/brianhawi/Documents/learning-projects/weather-app/node_modules/axios-mock-adapter/src/handle_request.js:69:11)
    at /Users/brianhawi/Documents/learning-projects/weather-app/node_modules/axios-mock-adapter/src/index.js:18:9
    at new Promise (<anonymous>)
    at MockAdapter.<anonymous> (/Users/brianhawi/Documents/learning-projects/weather-app/node_modules/axios-mock-adapter/src/index.js:17:14)
    at dispatchRequest (/Users/brianhawi/Documents/learning-projects/weather-app/node_modules/axios/lib/core/dispatchRequest.js:52:10)
    at <anonymous> config: [Object], response: [Object] } } ]
djalmaaraujo commented 6 years ago

Getting the same error as mentioned above

        mock = new MockAdapter(requestInstance)

        mock.onPost(`${SERVER_URL}/api/cart`).reply(200, 'cart')

        mock.onGet(`${SERVER_URL}/api/quoteDetails/product/`).reply(200, { data: {x: 2} })
davidlewallen commented 6 years ago

@djalmaaraujo What does your http request look like for POST and GET?

djalmaaraujo commented 6 years ago

@davidlewallen Not sure if I understood the question.

davidlewallen commented 6 years ago

@djalmaaraujo can you show me the code that is making the request to ${SERVER_URL}/api/car

djalmaaraujo commented 6 years ago

@davidlewallen

/**
 * Axios Request Instance (Move to config later)
 */
export const requestInstance = axios.create({
  baseURL: SERVER_URL,
  timeout: 20000,
  headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }
})
/**
 * Fetch QuoteDetails using ISBN
 */
export const buybackFetchQuoteDetails = (isbn) => {
  return (dispatch) => {
    if (!isbn) {
      return dispatch({
        type: 'BUYBACK/quoteDetailsFail',
        payload: 'Invalid ISBN, please try another ISBN.'
      })
    }

    dispatch({
      type: 'BUYBACK/loading'
    })

    return createOrReturnCart().then((cart) => {
      dispatch({
        type: 'BUYBACK/createOrReturnCart',
        payload: cart
      })

      return requestInstance.get(`api/quoteDetails/product/${isbn}`)
        .then((response) => {
          dispatch({
            type: 'BUYBACK/quoteDetailsSuccess',
            payload: response.data
          })
        })
        .catch((error) => {
          console.log('I ALWAYS GET CATCH HERE')
          dispatch({
            type: 'BUYBACK/quoteDetailsFail',
            payload: (error.response.data && error.response.data.error) ? error.response.data.error : 'Invalid ISBN, please try another ISBN.'
          })

          return error
        })
    })
  }
}
export const createOrReturnCart = () => {
  return requestInstance.post('api/cart')
    .then((response) => response.data)
    .catch(err => err)
}

Spec:

describe('async quote details', () => {
      let mock;
      let dispatchMockSpy;

      beforeEach(() => {
        mock = new MockAdapter(requestInstance)
        dispatchMockSpy = sinon.spy()

        mock
          .onPost(`${SERVER_URL}/api/cart`).reply(200, 'cart')
          .onGet(`${SERVER_URL}/api/quoteDetails/product/123456`).reply(200, 'quoteDetailsData')
      })

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

      it("should dispatch data", () => {
        const buybackFetchQuoteDetailsMock = buybackFetchQuoteDetails('123456')
        buybackFetchQuoteDetailsMock(dispatchMockSpy).then((x) => {
          expect(dispatchMockSpy.calledWith({
            type: 'BUYBACK/loading'
          })).toBeTruthy()

          expect(dispatchMockSpy.calledWith({
            type: 'BUYBACK/createOrReturnCart',
            payload: 'cart'
          })).toBeTruthy()
        }).catch((x) => console.log(x))
      })
    })
davidlewallen commented 6 years ago

@djalmaaraujo the code you link has no GET request to ${SERVER_URL}/api/cart or a POST request to ${SERVER_URL}/api/quoteDetails/product/

djalmaaraujo commented 6 years ago

@davidlewallen I updated the code above, please take a look. Just added what is requestInstance. In the code above, the /api/cart works, but the get, does not.

davidlewallen commented 6 years ago

@djalmaaraujo I think its how you are using the base url. You can console.log(mock.handlers.get) to see if the right url is saved in the array.

I just mocked this up and it works just fine isolated from Redux.

  fit('it should work', () => {
    const SERVER_URL = '/test';
    const requestInstance = axios.create({
      baseURL: SERVER_URL,
      timeout: 2000,
      headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
    });

    const newMock = new MockAdapter(requestInstance);
    newMock.onGet('/api/quoteDetails/product/123456').reply(200, 'quoteDetailsData');

    requestInstance.get('api/quoteDetails/product/123456')
      .then(response => console.log('response', response))
      .catch(error => console.log('error', error));
  });

Response console.log

 response { status: 200,
      data: 'quoteDetailsData',
      headers: undefined,
      config:
       { adapter: null,
         transformRequest: { '0': [Function: transformRequest] },
         transformResponse: { '0': [Function: transformResponse] },
         timeout: 2000,
         xsrfCookieName: 'XSRF-TOKEN',
         xsrfHeaderName: 'X-XSRF-TOKEN',
         maxContentLength: -1,
         validateStatus: [Function: validateStatus],
         headers:
          { Accept: 'application/json',
            'Content-Type': 'application/json' },
         baseURL: '/test',
         method: 'get',
         url: '/api/quoteDetails/product/123456',
         data: undefined } }
djalmaaraujo commented 6 years ago

@davidlewallen But why the /api/cart works and the other don't?

djalmaaraujo commented 6 years ago

I just console.log requestInstance

● Console

    console.log src/Actions/BuyBackActions.test.js:49
      { [Function: wrap]
        request: [Function: wrap],
        delete: [Function: wrap],
        get: [Function: wrap],
        head: [Function: wrap],
        options: [Function: wrap],
        post: [Function: wrap],
        put: [Function: wrap],
        patch: [Function: wrap],
        defaults:
         { adapter: [Function: xhrAdapter],
           transformRequest: [ [Function: transformRequest] ],
           transformResponse: [ [Function: transformResponse] ],
           timeout: 20000,
           xsrfCookieName: 'XSRF-TOKEN',
           xsrfHeaderName: 'X-XSRF-TOKEN',
           maxContentLength: -1,
           validateStatus: [Function: validateStatus],
           headers:
            { common: [Object],
              delete: {},
              get: {},
              head: {},
              post: [Object],
              put: [Object],
              patch: [Object],
              Accept: 'application/json',
              'Content-Type': 'application/json' },
           baseURL: 'http://localhost:8080/buyback' },
        interceptors:
         { request: InterceptorManager { handlers: [] },
           response: InterceptorManager { handlers: [] } } }
    console.log src/Actions/BuyBackActions.js:46
davidlewallen commented 6 years ago

@djalmaaraujo add console.log(JSON.stringify(mock.handlers, null, 2)) inside of your beforeEach but after the mock calls and post that up

djalmaaraujo commented 6 years ago

@davidlewallen

  ● Console

    console.log src/Actions/BuyBackActions.test.js:59
      {
        "get": [
          [
            "http://localhost:8080/buyback/api/quoteDetails/product/123456",
            null,
            200,
            "quoteDetailsData",
            null
          ]
        ],
        "post": [
          [
            "http://localhost:8080/buyback/api/cart",
            null,
            200,
            "cart",
            null
          ]
        ],
        "head": [],
        "delete": [],
        "patch": [],
        "put": []
      }

I was using onAny, just fixed. see now

djalmaaraujo commented 6 years ago

@davidlewallen The error returned in the catch (in the src code), is this:

{ Error: Request failed with status code 404
          at createErrorResponse (/Users/cooper/dev/valore/valore-buyback-webapp/client/node_modules/axios-mock-adapter/src/utils.js:110:15)
          at Object.settle (/Users/cooper/dev/valore/valore-buyback-webapp/client/node_modules/axios-mock-adapter/src/utils.js:90:16)
          at handleRequest (/Users/cooper/dev/valore/valore-buyback-webapp/client/node_modules/axios-mock-adapter/src/handle_request.js:55:11)
          at /Users/cooper/dev/valore/valore-buyback-webapp/client/node_modules/axios-mock-adapter/src/index.js:16:9
          at new Promise (<anonymous>)
          at MockAdapter.<anonymous> (/Users/cooper/dev/valore/valore-buyback-webapp/client/node_modules/axios-mock-adapter/src/index.js:15:14)
          at dispatchRequest (/Users/cooper/dev/valore/valore-buyback-webapp/client/node_modules/axios/lib/core/dispatchRequest.js:59:10)
          at <anonymous>
        config:
         { adapter: null,
           transformRequest: { '0': [Function: transformRequest] },
           transformResponse: { '0': [Function: transformResponse] },
           timeout: 20000,
           xsrfCookieName: 'XSRF-TOKEN',
           xsrfHeaderName: 'X-XSRF-TOKEN',
           maxContentLength: -1,
           validateStatus: [Function: validateStatus],
           headers:
            { Accept: 'application/json',
              'Content-Type': 'application/json' },
           baseURL: 'http://localhost:8080/buyback',
           method: 'get',
           url: '/api/quoteDetails/product/123456',
           data: undefined },
        response:
         { status: 404,
           config:
            { adapter: null,
              transformRequest: [Object],
              transformResponse: [Object],
              timeout: 20000,
              xsrfCookieName: 'XSRF-TOKEN',
              xsrfHeaderName: 'X-XSRF-TOKEN',
              maxContentLength: -1,
              validateStatus: [Function: validateStatus],
              headers: [Object],
              baseURL: 'http://localhost:8080/buyback',
              method: 'get',
              url: '/api/quoteDetails/product/123456',
              data: undefined },
           data: undefined } }
davidlewallen commented 6 years ago

@djalmaaraujo try removing the ${SERVER_URL} and see if that helps?

djalmaaraujo commented 6 years ago

@davidlewallen Same thing. Still have the /api/cart working, but not the other request. The difference is that this request is inside another promise. maybe that's the reason?

davidlewallen commented 6 years ago

@djalmaaraujo hmm I am seeing something a little different on my end. Try adding this to createOrReturnCart function:

const createOrReturnCart = () => requestInstance.post('/api/cart')
      .then(response => response)
      .catch((error) => {
        console.log('error', error)
        return error;
      });

And see if you get an error there?

djalmaaraujo commented 6 years ago

@davidlewallen I changed a little bit the code to show you the test results:

/**
 * Fetch QuoteDetails using ISBN
 */
export const buybackFetchQuoteDetails = (isbn) => {
  return (dispatch) => {
    if (!isbn) {
      return dispatch({
        type: 'BUYBACK/quoteDetailsFail',
        payload: 'Invalid ISBN, please try another ISBN.'
      })
    }

    dispatch({
      type: 'BUYBACK/loading'
    })

    return createOrReturnCart().then((cart) => {
      console.log('CART PASSED')
      dispatch({
        type: 'BUYBACK/createOrReturnCart',
        payload: cart
      })

      return requestInstance.get(`api/quoteDetails/product/${isbn}`)
        .then((response) => {
          console.log('QUOTE DETAILS RESOLVED')
          dispatch({
            type: 'BUYBACK/quoteDetailsSuccess',
            payload: response.data
          })
        })
        .catch((error) => {
          console.log('ERROR IN QUOTE DETAILS')
          dispatch({
            type: 'BUYBACK/quoteDetailsFail',
            payload: (error.response.data && error.response.data.error) ? error.response.data.error : 'Invalid ISBN, please try another ISBN.'
          })

          return error
        })
    })
  }
}

const createOrReturnCart = () => requestInstance.post('/api/cart')
  .then(response => response)
  .catch((error) => {
    console.log('error', error)
    return error;
  })

SPEC:

 PASS  src/Containers/BuyBackContainer/BuyBackContainer.test.js
 PASS  src/Actions/BuyBackActions.test.js
  ● Console

    console.log src/Actions/BuyBackActions.js:32
      CART PASSED
    console.log src/Actions/BuyBackActions.js:47
      ERROR IN QUOTE DETAILS
    console.log src/Actions/BuyBackActions.test.js:81
      { Error: expect(received).toBeTruthy()

      Expected value to be truthy, instead received
        false
          at buybackFetchQuoteDetailsMock.then.x (/Users/cooper/dev/valore/valore-buyback-webapp/client/src/Actions/BuyBackActions.test.js:75:11)
          at <anonymous> matcherResult: { message: [Function], pass: false } }

Test Suites: 2 passed, 2 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.938s, estimated 1s
Ran all test suites related to changed files.

Watch Usage: Press w to show more.
davidlewallen commented 6 years ago

@djalmaaraujo One last thing before I give up. Make these changes

mock
  .onPost('/api/cart').reply(200, 'cart')
  .onGet(/api\/quoteDetails\/product\/.+/).reply(200, 'quoteDetailsData')

See if that give any better results

djalmaaraujo commented 6 years ago

@davidlewallen Sorry the delay, but still failing. I will give up, check for another alternative. Thanks for your time, honestly.

djalmaaraujo commented 6 years ago

@davidlewallen I was able to make it work. I split the method into 2 methods and tested separately. I can't tell you exactly what is the issue, but I believe it's a bug from this mock-adapter, when you have a nested promise level, it's like the mock is not aware of that request.

djalmaaraujo commented 6 years ago

@davidlewallen After another time facing the same issue, I think the solution is to pass (done) and wait for the correct ajax to finish.

Check this example, and it works:

it('should return cartItem data and dispatch BUYBACK/cartAddItem', (done) => {
      mock
        .onPost('/api/cart').reply(200, { id: 5 })
        .onPost('/api/cart/addItem', {cartId: 5, isbn: 123456}).reply(200, expectedResponse)

      buybackAddItemToCart(5, {productCode: '123456'}, dispatchMockSpy)
        .then(() => {
          expect(dispatchMockSpy.calledWith({
            type: 'BUYBACK/cartLoading'
          })).toBeTruthy()

          expect(dispatchMockSpy.args[1][0].payload).toEqual({
            cartItem: expectedResponse,
            quoteDetails: { productCode: '123456' }
          })

          done()
        })
        .catch(err => {
          done.fail('Should not call catch')
          console.log(err)
        })
    })

In this case, I am always getting 404 for the /api/cart... but with the done, now it works.

pmgmendes commented 6 years ago

Hi, Sorry for reviving the thread but because its still open it could make sense to reuse it as I'm facing the same issues described by @kirankbs, @HawiCaesar and @djalmaaraujo.

I'm not able to get it to work even with @djalmaaraujo's solution using the done callback.

What could be missing here?

Dependencies: axios@0.15.3 axios-mock-adapter@1.15.0

The method under test:

export const createResource = field => {
  return axios
    .post(
      "http://app.io/api/resource",
      JSON.stringify({
        field
      })
    );
};

The test:

it("should create a resource", () => {
  const fieldValue = "field value", 
    resourceName = "resource name";

  new MockAdapter(axios)
    .onPost("http://app.io/api/resource", { field: fieldValue })
    .reply(200, { field: fieldValue, name: resourceName});

  return createResource(field).then(({ field, name }) => {
    expect(field).toBe(fieldvalue);
    expect(name).toBe(resourceName);
  }).catch(error => {
    console.error(error);
    return error;
  });
});

The error:

{ Error: Request failed with status code 404
  at createErrorResponse (/home/pmgmendes/repo/node_modules/axios-mock-adapter/src/utils.js:117:15)
  at Object.settle (/home/pmgmendes/repo/node_modules/axios-mock-adapter/src/utils.js:97:16)
  at handleRequest (/home/pmgmendes/repo/node_modules/axios-mock-adapter/src/handle_request.js:73:11)
  at /home/pmgmendes/repo/node_modules/axios-mock-adapter/src/index.js:18:9
  at new Promise (<anonymous>)
  at MockAdapter.<anonymous> (/home/pmgmendes/repo/node_modules/axios-mock-adapter/src/index.js:17:14)
  at dispatchRequest (/home/pmgmendes/repo/node_modules/axios/lib/core/dispatchRequest.js:52:10)
  at <anonymous>
  at process._tickCallback (internal/process/next_tick.js:118:7)
config: 
  { transformRequest: { '0': [Function: transformRequest] },
    transformResponse: { '0': [Function: transformResponse] },
    timeout: 0,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    validateStatus: [Function: validateStatus],
    headers: 
    { Accept: 'application/json, text/plain, */*',
      'Content-Type': 'application/x-www-form-urlencoded' },
    method: 'post',
    url: 'http://app.io/api/resource',
    data: '{"field":"field value"}' 
  },
response: 
  { status: 404,
    config: 
    { transformRequest: [Object],
      transformResponse: [Object],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      validateStatus: [Function: validateStatus],
      headers: [Object],
      method: 'post',
      url: 'http://app.io/api/resource',
      data: '{"field":"field value"}' 
    },
    data: undefined 
  } 
}
william-bratches commented 6 years ago

+1

I'm using axios in a node service layer that utilizes axios.create to put a common baseUrl in all axios requests. The mock-adapter seems to failing to work in my case as well.

mdiflorio commented 6 years ago

Getting exactly the same issue with a nested promise.

pmgmendes commented 6 years ago

After debugging session focused on utils.js#findHandler() I came to conclusion that the generated POST body didn't match the expected payload provided to the axios mock thus causing the 404.

The functions used within findHandler method would benefit immensely by having some verbose logging. It would be much easier to understand what's the real reason behind a possible 404.

jd-carroll commented 5 years ago

What version of node are people using? I am also facing the same issue and I believe I have chased it down to Utils.findHandler.

My environment:

> process.versions
{ ...
  node: '8.9.4',
  v8: '6.1.534.50',
  ... }

To demonstrate the issue, go into your node_modules and update your utils.js to the following:

function find(array, predicate) {
  var length = array.length;
  for (var i = 0; i < length; i++) {
    var value = array[i];
    const pred = predicate(value);
    if (pred) return value;
    else console.log(`Failed to match value: ${pred}`);
  }
}

// ...

function isBodyOrParametersMatching(method, body, parameters, required) {
  console.log('isBodyOrParametersMatching');
// ...

What you'll see is that the predicate fails to match with a log statement of: Failed to match value: undefined Further, you will not see isBodyOrParametersMatching printed in the logs either.

My theory is that there is a bug in the way V8 handles [arbitrarily] complex bitwise statements. I've seen this behavior (where it returns undefined instead of a boolean) in another of our internal projects (completely unrelated to axios-mock-adapter).

Once you confirmed the behavior above, try replacing the findHandler method with the following:

function findHandler(handlers, method, url, body, parameters, headers, baseURL) {
  console.log(arguments);
  console.log(handlers);
  return find(handlers[method.toLowerCase()], function(handler) {
    let urlMatch = false;
    if (typeof handler[0] === 'string') {
      urlMatch = isUrlMatching(url, handler[0]) || isUrlMatching(combineUrls(baseURL, url), handler[0]);
    } else if (handler[0] instanceof RegExp) {
      urlMatch = handler[0].test(url) || handler[0].test(combineUrls(baseURL, url));
    }
    const bodyParamMatch = urlMatch && isBodyOrParametersMatching(method, body, parameters, handler[1]);
    const headersMatch = bodyParamMatch && isRequestHeadersMatching(headers, handler[2]);
    return headersMatch
  });
}

You'll notice that isBodyOrParametersMatching is printed in the logs as expected and the predicate matches successfully.

My theory is that there is a bug in the way the V8 runtime parse boolean statements. Specifically, when the V8 runtime parses a bitwise boolean statement where there is a parentheses group followed by a method invocation. For whatever reason, I believe V8 falls on its face and returns undefined where that value should otherwise be unattainable.

As I said earlier, I've seen this in an internal ReactJS app (which was not using axios) running in the latest Chrome browser. I'll see if I can put together a sample app to test my theory.

I'll also put together a PR to solve this issue (with the updated findHandler method). Though it will have to wait until I get home tonight as we are unable to push anything to github from our office 😢

PS.- If someone else wants to take the code above and create a PR before I do, please feel free. (Just make sure to reference this issue)

jd-carroll commented 5 years ago

Note: An issue has been opened within V8 https://bugs.chromium.org/p/v8/issues/detail?id=7949

beliolfa commented 5 years ago

I get the 404 randomly! If I chain three onGetmethods it works, next time I get 404 then unchain the methods and it works. Repat to infinite.

I'm lost. It is as it were working when HMR but not working on refresh (I am working with storybook btw)

See this video http://recordit.co/Hj45OY8yjB

mvsousa commented 5 years ago

I'm facing the same error when using onPost.

sagaban commented 5 years ago

I had this problem in my tests and solved it creating just one instance of the mock object: export const mock = new MockAdapter(axios); and then just import { mock } from "../../utils/mockAdapter";

s8sachin commented 5 years ago

For anyone who is still stuck in this, I had same problem, i was passing data to mock.onPost(), just remove the data part.. it should work fine

mock.onPost('http://localhost:8000/auth', {data: someData})
    .reply(200);

to

mock.onPost('http://localhost:8000/auth')
    .reply(200);
gkohen commented 5 years ago

@s8sachin , what do you do if you need to a assert certain data was returned?

gkohen commented 5 years ago

@sagaban , you're suggestion has worked perfectly. Just remember to run the mock.reset() in your afterEach. Thank you.

s8sachin commented 5 years ago

@gkohen , passing data didn't matter in my case since i'm writing mock.onPost().reply() on each of test cases... so that each of them have different replies based on that particular test case... I guess sending data doesn't matter since we send required reply no matter what. and, does using @sagaban 's answer, is is possible to send data and not receive 404 ?

toymachiner62 commented 5 years ago

I had this problem in my tests and solved it creating just one instance of the mock object: export const mock = new MockAdapter(axios); and then just import { mock } from "../../utils/mockAdapter";

This throws an obvious error for me that mock has already been declared. What am I not understanding?

Here's my code:

const axios = require('axios')
const MockAdapter = require('axios-mock-adapter')
let mock = new MockAdapter(axios)
const { mock } = require( "../../utils/mockAdapter")
const { mock } = require( "../../utils/mockAdapter")
        ^

SyntaxError: Identifier 'mock' has already been declared
sagaban commented 5 years ago

I had this problem in my tests and solved it creating just one instance of the mock object: export const mock = new MockAdapter(axios); and then just import { mock } from "../../utils/mockAdapter";

This throws an obvious error for me that mock has already been declared. What am I not understanding?

Here's my code:

const axios = require('axios')
const MockAdapter = require('axios-mock-adapter')
let mock = new MockAdapter(axios)
const { mock } = require( "../../utils/mockAdapter")
const { mock } = require( "../../utils/mockAdapter")
        ^

SyntaxError: Identifier 'mock' has already been declared

First of all: I don't know if my approach is correct. That being said: you have to initialize and export the mock instance in the utils/mockAdapter.js file and then import that instance in the file where you want to use it. e.g.


// tests/unit/utils/mockAdapter.js
import Vue from "vue";
import VueAxios from "vue-axios";
import MockAdapter from "axios-mock-adapter";
import axios from "axios";

Vue.use(VueAxios, axios);

export const mock = new MockAdapter(axios);

and
``` js
//tests/unit/store/actions.spec.js
import { mock } from "../utils/mockAdapter";
rayoplateado commented 5 years ago

Hi all!

Is there any news on this issue? I'm facing the same problem.

Thank you all!

shaoyao commented 5 years ago

I had this problem in my tests, if don't pass data like @s8sachin ,it can be work

softmixt commented 5 years ago

I had same issues and by having mockAdapter in a single instance file and then import it in all other mock files did the trick .

so create a file mocks/mockInstance.js

import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';

export default new MockAdapter(axios);

then use it like this :

import mock from 'mocks/mockInstance';

 mock.onGet(getRolesURI).reply(200, {
    roles: fakeRolesTable,
  });
vsakaria commented 5 years ago

Fix this issue by making sure the mocked payload was identical to the payload used to make the actual call. Attention to details my friends 😄

vsakaria commented 5 years ago

Which coincidentally makes sense since axios matches a request by its URL and Payload. If a URL is the same but the payload is different then its a different request. Also if you have two requests with the same URL and Payload then technically the response should be the same.

toymachiner62 commented 5 years ago

I'm either blind, dumb, or both. This looks to match exactly, but doesn't work. Same case I always seem to have with axios-mock-adapter for quite some time now. https://stackoverflow.com/questions/54638466/axios-mock-adapter-not-matching-url-as-expected

zarapustra commented 5 years ago

in my case running npm test instead of npm run test and replacing the URL with the correct value in an env variable used in the code.

mei33 commented 5 years ago

Hi! I have same issue - I have two tests in describe block and each of them runs perfectly, but running both of them leads to fail. My current approach is:

_axiosMockAdapter.js:

import Vue from "vue";
import VueAxios from "vue-axios";
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';

Vue.use(VueAxios, axios);

export default new MockAdapter(axios);

test.js:

import mockAxios from './_axiosMockAdapter'
import axios from 'axios';
import global from '../global';

describe('accessing to server', () => {
    it('sends data on "Save" to predefined url', async done => {
        const wrapper = mount(global);
        const url = wrapper.vm.$data.url;

        const saveButton = wrapper.find({ref: 'save'});
        const fakeData = {...some data...}

        wrapper.vm.$nextTick(() => {
            saveButton.trigger('click');
            expect(spyAxiosOnPut).toHaveBeenCalledWith(url, {...fakeData});
            const spyAxiosOnPut = jest.spyOn(axios, 'put');
            spyAxiosOnPut.mockClear();
             done();
        });
    })

    it('fetches data from predefined url when created', async done => {
        const flushPromises = () => new Promise(resolve => setTimeout(resolve))
        const wrapper = mount(global);
        const url = wrapper.vm.$data.url;
        const fakeData = {...some data...}

        mockAxios.onGet(url).reply(200, fakeData);

        await flushPromises();

        wrapper.vm.$nextTick(() => {
               expect(mockAxios.history.get[0].url).toEqual(url);
               expect(wrapper.vm.$data.data).toEqual(fakeData);
               done();
         })
    })
})

Actually, it seems to me i tried all of the methods in this discussion - i used axios strictly from package, i imported it from other file (now i use this method), I tried to reset it on each iteration - didn't help either:

describe('accessing to server', () => {
            let mockAxios;

            beforeAll(() => {
                mockAxios = new MockAdapter(axios)
            });

            afterEach(() => {
                mockAxios.reset()
            });

            afterAll(() => {
                mockAxios.restore()
            });
....

Even run yarn test and yarn run test What am I doing wrong? I am using rails webpacker - not quite sure if it makes sense for current issue. All other non-mocking-axios tests are passing fine, problem is in running multiple mocking-axios tests.

kylemh commented 5 years ago

Aside from only exporting one mock instance, I've also been bitten in the butt when I use the spread operator in the body and the object contains extra fields than expected. Just wanted to share.

I really wish we'd see some error a la: Expected mock adapter to have PATH be requested with BODY_A, but was requested with BODY_B.

ndraiman commented 4 years ago

I'm having the same error.

I tried the following:

adapter.onPost(`${baseUrl}/endpoint`).reply(config => {
    console.log('config', config);
    return [200];
});

This return the 404 error: Error: Request failed with status code 404

But dropping the url matcher works:

adapter.onPost().reply(config => {
    console.log('config', config);
    return [200];
});

Note that config.url matches the value of `${baseUrl}/endpoint`

sohymg commented 4 years ago

I faced the same error and the solution from @vsakaria worked for me. Basically the payload wasn't matching that's why it will also work if you remove the payload from mock.onPost() like @s8sachin mentioned.

Jackman3005 commented 3 years ago

I spent awhile on a bug today where we had accidentally passed in incorrect data in an onPost body and were receiving a 404 when our app code tried to make the call with the correct data... I found that the 404 was a difficult message to work with. Because this particular test had multiple API calls, I couldn't sort out which one it was immediately, and I had to fiddle with things until I noticed my mistake. In fact... I ended up console.logging every parameter to the utils.createAxiosError function and that's where I found our mistake...

Is there anyway that we could get more output for this situation? Like "Hi you sent me this payload, header, url, etc but that doesn't match anything you've set up for me." If being extra ambitious, we could show them the closes matches to the config MockAdapter received. I'd be happy to help with this, but I just naively felt like I want to put console logs in the utils.createAxiosError block, which clearly can't be the best approach (also noticing the toJSON() function definition there, how do you expect that to be used?)

Here is a code sample that illustrates the situation:

Test Setup Code

import axios from 'axios'
const mockAdapter = new MockAdapter(axios)
mockAdapter.onPost('/api/my-url', {
  something: [
    {
      i: "hope",
      that: "this",
      matches: "whoops-typo"
    }
  ]
}).replyOnce(200)

App Code

await axios
  .post('/api/my-url/', {
    something: [
      {
        i: "hope",
        that: "this",
        matches: "but-it-wont"
      }
    ]
  })

Resulting Message

Error: Request failed with status code 404

    at createAxiosError (/Users/hello/some-project/node_modules/axios-mock-adapter/src/utils.js:148:15)
    at Object.settle (/Users/hello/some-project/node_modules/axios-mock-adapter/src/utils.js:127:11)
    at handleRequest (/Users/hello/some-project/node_modules/axios-mock-adapter/src/handle_request.js:126:15)
    at /Users/hello/some-project/node_modules/axios-mock-adapter/src/index.js:26:9
    at new Promise (<anonymous>)
    at MockAdapter.<anonymous> (/Users/hello/some-project/node_modules/axios-mock-adapter/src/index.js:25:14)
    at dispatchRequest (/Users/hello/some-project/node_modules/axios/lib/core/dispatchRequest.js:59:10)

Potential Desired Message

Error: Request did not match any stubbed request and pass-through mode is turned off!

   ...
   at myAppMethod (/Users/..../myAppAxiosCall.js:44)
   ...

   The un-matched request: {
        method: 'POST',
        url: '/api/my-url',
        body: {
          something: [
             {
                i: "hope",
                that: "this",
                matches: "but-it-wont"
             },
          ]
        },
        headers: ...

Thank you for reading this far. I apologize if this is not the thread to have posted this request. I am happy to make a new issue. Again, I would consider helping out if I had the guidance for what you would prefer.