Search code examples
node.jsmockingjestjsaxiosava

How to mock two axios call in same endpoint


Example:

In my test I need to mock the two calls to the external apis. I can't mock on the second call. With the library 'axios-mock-adapter' and ava I could only mock the first call.

const read = async (req, res) => {
  try {
    const responseOne = await axios({
      method: 'get',
      url: 'https://api.example.com/search/repositories',
      params: {
        q: 'example',
      },
    })
    const resultOne = responseOne.data
    if (resultOne) {
      let aux = []
      const itemsOne = resultOne.items.slice(0, 10)
      return Promise.all(
        itemsOne.map((p, indexOne) => {
          aux = [...aux, { id: p.id, name: p.name, arr: [] }]
          return axios({
            headers: { Authorization: `Bearer ${SECRET_KEY}` },
            method: 'get',
            url: 'https://api.example2.com/search/things',
            params: { q: p.name },
          })
            .then(responseTwo =>
              responseTwo.data.array.map(i => {
                aux[indexOne].arr.push({
                  id: i.id,
                  created_at: i.created_at,
                  text: i.text,
                })
              })
            )
            .catch(err => res.status(500).json(err))
        })
      )
        .then(() => res.json({ result: aux }))
        .catch(err => res.status(500).json(err))
    } else res.status(404).json({ message: 'Example not found' })
  } catch (err) {
    res.status(500).json(err)
  }
}

TEST

This is my test read.test.js. Now I am dealing with the 'ava' and 'axios-mock-adapter' libraries. But the second axios call does not working the mock.

import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
import test from 'ava'
import read from '../read'

var mock = new MockAdapter(axios)


test('GET - /examples', async t => {
  mock
    .onGet('https://api.example.com/search/repositories, {
      params: {
        q: 'example',
      },
    })
    .reply(200, {
      total_count: 2,
      incomplete_results: false,
      items: [
        {
          id: 555,
          name: 'exampleOne',
        },
        {
          id: 666,
          name: 'exampleTwo',
        },
      ],
    })
    .onGet('https://api.example2.com/search/things', {
      params: { q: 'name' },
      headers: { Authorization: `Bearer ${SECRET_KEY}` },
    })
    .reply(200, {
      statuses: [
        {
          id: 123,
          created_at: 'Thu Feb 27 22:54:02 +0000 2020',
          text: 'Example one.',
        },
        {
          id: 456,
          created_at: 'Wed Feb 26 13:40:20 +0000 2020',
          text: 'Example Two',
        },
      ],
    })

  try {
    const res = await read()
    t.assert(res.result[0].id.test(555))
  } catch (err) {
    console.log(`ERROR::: ${err}`)
  }

Solution

  • The problem with the mock of the second request is that you are configuring it to respond only if the endpoint is requested with the params { q: 'name' }. If you want to respond with the same data for all the requests to https://api.example2.com/search/things it is enough to remove that params atribute:

    .onGet('https://api.example2.com/search/things', {
        headers: { Authorization: `Bearer ${SECRET_KEY}` },
    })
    .reply(200, {
        statuses: [
            {
                id: 123,
                created_at: 'Thu Feb 27 22:54:02 +0000 2020',
                text: 'Example one.',
            },
            {
                id: 456,
                created_at: 'Wed Feb 26 13:40:20 +0000 2020',
                text: 'Example Two',
            },
        ],
    })
    

    There are also other problems present in your test. In the mocked response you are passing the data in a property statuses but in your actual code you are iterating over an array property.

    Also, the read method receives two parameters, req and res. If you don't provide those objects when calling read in your test it will break, as you are calling methods of the res object.

    The simplest way to call the method would be by creating fake req and res objects and calling the read method with them. For the sake of simplicity, I will create the mock with the help of sinon spies:

    test('GET - /examples', async t => {
      mock
        .onGet('https://api.example.com/search/repositories, {
          params: {
            q: 'example',
          },
        })
        .reply(200, {
          total_count: 2,
          incomplete_results: false,
          items: [
            {
              id: 555,
              name: 'exampleOne',
            },
            {
              id: 666,
              name: 'exampleTwo',
            },
          ],
        })
        .onGet('https://api.example2.com/search/things', {
          headers: { Authorization: `Bearer ${SECRET_KEY}` },
        })
        .reply(200, {
          array: [
            {
              id: 123,
              created_at: 'Thu Feb 27 22:54:02 +0000 2020',
              text: 'Example one.',
            },
            {
              id: 456,
              created_at: 'Wed Feb 26 13:40:20 +0000 2020',
              text: 'Example Two',
            },
          ],
        })
    
      const res = {
        status: sinon.stub().returnsThis(),
        json: sinon.stub().returnsThis(),
      };
    
      try {
        await read({}, res);
        const data = res.json.firstCall.args[0];
        t.is(data.result[0].id, 555)
      } catch (err) {
        console.log(`ERROR::: ${err}`)
      }
    });
    

    Note that I'm mocking the request object by passing an empty object, since the read method does not use any property of the request.