Leon Gaban
Leon Gaban

Reputation: 39018

Stateful React component with async function failing Jest test

I'm following along in this Jest test tutorial on Pluralsight here. And I've written code exactly like the author, but for some reason my test is not passing.

My Pull request to the author's repo: https://github.com/danielstern/isomorphic-react/pull/19

I have a simple React component, it updates it's count state with a async/await call to a service within the componentDidMount.

{this.state.count != -1 ? `${this.state.count} Notifications Awaiting` : 'Loading...'}

EXPECTED

Since I've mocked the NotificationsService, and set count to 42, the test should pass with text inside the component being "42 Notifications Awaiting!"

RESULTS

The text stays stuck as the default Loading...

I've mocked the service correctly, and the count variable is even being logged correctly as 42! However this.state.count is still -1 so instead of displaying: ${this.state.count} Notifications Awaiting it still displays Loading... and thus fails the test.


What I've tried

1) I've tried adding 1000 into the delay.

2) Tried using setTimeout inside the test.

3) Tried jest.useFakeTimers(); and jest.runAllTimers();

However nothing is working, the count inside of the component is stuck at -1 even though count is set to 42. It just seems to me that my test is running before the state is finished being set?

enter image description here

The NotificationsViewser.jsx Component

import React from 'react';
import NotificationsService from '../services/NotificationsService';

export default class componentName extends React.Component {
  constructor(...args) {
    super(...args);

    this.state = {
      count: -1
    }
  }

  async componentDidMount () {
    let { count } = await NotificationsService.GetNotifications();
    console.log('count:', count);
    this.setState({
      count
    });
  }

  componentDidUpdate() {
    console.log('componentDidUpdate:', this.state);
  }

  render() {
    return (
      <div className="mt-3 mb-2">
        <div className="notifications">
          {this.state.count != -1 ? `${this.state.count} Notifications Awaiting` : `Loading...`}
        </div>
      </div>
    )
  }
}

NotificationsService.js

import { delay } from 'redux-saga';

export default {
  async GetNotifications() {
    console.warn("REAL NOTIFICATION SERVICE! CONTACTING APIS!");

    await delay(1000);
    return { count: 42 };
  }
}

mocks: NotificationsService.js

let count = 0;

export default {
  __setCount(_count) {
    count = _count;
  },
  async GetNotifications() {
    console.warn("GOOD JOB! USING MOCK SERVICE");
    return { count };
  }
}

Finally...

The Test

import React from 'react';
import renderer from 'react-test-renderer';
import delay from 'redux-saga';

import NotificationsViewer from '../NotificationsViewer';

jest.mock('../../services/NotificationsService');

const notificationService = require('../../services/NotificationsService').default;

describe('The notification viewer', () => {

  beforeAll(() => {
    notificationService.__setCount(42);
  });

  it('should display the correct number of notifications', async() => {
    const tree = renderer
      .create(
        <NotificationsViewer/>
      );

    await delay();

    const instance = tree.root;
    const component = instance.findByProps({className: `notifications`});
    const text = component.children[0];
    console.log('text is:', text);

    expect(text).toEqual('42 Notifications Awaiting!');
  });
})

Upvotes: 3

Views: 969

Answers (3)

Leon Gaban
Leon Gaban

Reputation: 39018

The problem was that await delay() did not work to let all the React life cycle methods like componentDidMount instantiate / get called.

I had to result to using Enzyme even though the author did not recommend it due to a lot of open bug issues.

Using Enzyme I could ensure that the componentDidMount was called, and thus setting the state of count to 42 with the mocked service.

I also needed to installed the following packages:

"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",

Fixed test

import React from 'react';
import renderer from 'react-test-renderer';
import Adapter from 'enzyme-adapter-react-16';
import { shallow, configure } from 'enzyme';

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

import NotificationsViewer from './NotificationsViewer';

jest.mock('../services/NotificationsService');

const notificationService = require('../services/NotificationsService').default;

notificationService.default = jest.fn();

describe('The notification viewer', () => {

  beforeAll(() => {
    notificationService.__setCount(42);
  });

  // it('pass', () => {});

  it('should display the correct number of notifications', async() => {
    const tree = renderer.create(<NotificationsViewer />);
    const wrapper = shallow(<NotificationsViewer />);
    const instance = tree.root;

    await wrapper.instance().componentDidMount();

    const component = instance.findByProps({className: `notifications`});
    const text = component.children[0];
    console.log('text is:', text);

    expect(text).toEqual('42 Notifications Awaiting');
  });
})

enter image description here

Upvotes: 1

Juliano Padilha
Juliano Padilha

Reputation: 26

In fact, the real problem was in isomorphic-react/src/components/__tests__/NotificationsViewer.js file. The delay are imported the wrong way and that was causing the error on test.

If importing delay like this: import { delay } from 'redux-saga' fix the problem. =D

Upvotes: 1

paulinhorocha
paulinhorocha

Reputation: 470

Maybe your describe also needs to be an async function? await statements need to be declared inside an async scope, no?

Upvotes: 0

Related Questions