1

In my react-native app I want to make a post request to my server, and depending on the result, I want to show a message. I'd like to unit-test this using jest, mocking away the call to the backend.

I tried to create a minimal example to show what's going on:

This is the Component I am testing

import React from 'react';
import { Alert } from 'react-native';
import { create } from 'apisauce';

const api = create({ baseURL: "someurl" });

export class ApisauceExample extends React.Component {

  upload() {
    api.post('/upload')
      .then(() => { Alert.alert('Success!', 'Thanks, dude!' ); })
      .catch(() => { Alert.alert('Error!', 'Something went wrong!' ); });
  }

  render() {
    this.upload();
    return null;
  }
}

So far, so easy. Let's test the positive POST behavior with a unit test where I mock the module:

A working unit test with a global mock:

import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { configure, shallow } from 'enzyme';
import { ApisauceExample } from '../src/ApisauceExample';

configure({ adapter: new Adapter() });

const mockAlert = jest.fn();
jest.mock('Alert', () => ({
  alert: mockAlert
}));

let mockPostResult = jest.fn();
jest.mock('apisauce', () => ({
  create: () => ({
    post: (url) => new Promise((resolve) => {
      mockPostResult(url);
      resolve();
    })
  })
}));

describe('ApisauceExample', () => {
  it('should call create and alert', async () => {
    await shallow(<ApisauceExample />);
    expect(mockPostResult).toHaveBeenCalledTimes(1);
    expect(mockAlert).toHaveBeenCalledWith('Success!', 'Thanks, dude!');
  });
});

Yep, that works perfectly. But now, I want to test whether my code behaves properly in both a successful POST and an unsuccessful one. I learned that you can change the mocks with jest by using the mockImplementation function. (see for example Jest Mock module per test). I have already used that in other tests, but here it will simply not work:

Not working unit test with different mock behavior per test case

import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { configure, shallow } from 'enzyme';
import { ApisauceExample } from '../src/ApisauceExample';

configure({ adapter: new Adapter() });

const mockAlert = jest.fn();
jest.mock('Alert', () => ({
  alert: mockAlert
}));

jest.mock('apisauce', () => ({
  create: jest.fn()
}));
import { create } from 'apisauce';

describe('ApisauceExample2', () => {
  it('should call create and alert', async () => {
    create.mockImplementation(() => ({
      post: (url) => new Promise((resolve, reject) => {
        reject("error");
      })
    }));
    await shallow(<ApisauceExample />);
    expect(mockAlert).toHaveBeenCalledWith('Error!', 'Something went wrong!');
  });
});

This leads to:

Failed: Cannot read property 'post' of undefined
  TypeError: Cannot read property 'post' of undefined
  at ApisauceExample.upload (src/ApisauceExample.js:10:4)
  at ApisauceExample.render (src/ApisauceExample.js:16:6)

And I just don't understand what is going on. In my opinion, the second mock is the same as the first one (except for the reject instead of the resolve).

Do you see what is going on here? I'd really appreciate any hints.

Thanks so much and have a nice day!

skyboyer
  • 22,209
  • 7
  • 57
  • 64
konse
  • 885
  • 1
  • 10
  • 21

1 Answers1

0

Try adding the axios-mock-adapter to your setup, and remember to mock api.apisauce.axiosInstance and not API (apisauce exposes the internal axios instance through api.apisauce.axiosInstance

so:

test('mock adapter', async () => {
  const api = API.create()
  const mock = new MockAdapter(api.axiosInstance)
  mock.onGet('/someEndpoint').reply(201, {
    re: 'superstar'
  })
  const response = await api.getSomeEndpoint()
  expect(response.ok).toBe(true)
  expect(response.status).toBe(201)
  expect(response.data).toEqual({ re: 'superstar' })
})

and in your api

import apisauce from 'apisauce'

... some code

const create = (baseURL = 'https://api.github.com/') => {
  const api = apisauce.create()
  const getSomeEndpoint = () => api.get('some_endpoint')

  return {
    getSomeEndpoint,
    axiosInstance: api.axiosInstance
  }
}

export default {
  create
}

Quango
  • 12,338
  • 6
  • 48
  • 83
klh
  • 1