0

I'm trying to get my head around functional testing in React and I've hit a bit of a blocker which I wish someone can shed some light on. Currently, I have a little counter application which has a button component that receives a function and a string as props. that looks like this:

Button.js

import React from 'react'
import PropTypes from 'prop-types'

export const Button = (props) => {
  const { btnTitle, btnAction } = props

  return (
    <button onClick={btnAction}>{btnTitle}</button>
  )
}

Button.propTypes = {
  btnAction: PropTypes.func,
  btnTitle: PropTypes.string
}

I also have a helpers directory that contains my CounterHandler function which receives a number as the counter initial value and can either increase or decrease said initial value. The code looks as follows:

CounterHandler.js

import React from 'react'

export const CounterHandler = num => {
  const [counter, setCounter] = React.useState(num)
  const increase = () => setCounter(counter + 1)
  const decrease = () => setCounter(counter - 1)

  return {
    counter,
    increase,
    decrease
  }
}

now my App.js which renders the button and the action code looks like this.

App.js

import React from 'react'
import CounterHandler from './components/button/helpers'
import Button from './components/button'

function App () {
  const counter = CounterHandler(0)

  return (
    <div className="App">
      <Button btnTitle="click to increase" btnAction={counter.increase} />
      <Button btnTitle="click to decrease" btnAction={counter.decrease} />
      <h1>counter: {counter.counter}</h1>
    </div>
  )
}

export default App

The application works as it's intended. The counter will increase or decrease based on which button was pressed.

Now I'm attempting to write a test for my CounterHandler.js function, but unfortunately, I keep hitting a hooks error that does not occur when running the application on my local server.

So far the only test I wanted to try is to get the initial value found in my counter and continue from there. My test looks like this:

CounterHandler.test.js

import { CounterHandler } from './CounterHandler'

const counter = CounterHandler(0)

describe('Counter state', () => {
  test('test initial state', () => {
    expect(counter.counter).tobe(0)
  })
})

and the output I'm getting is:

enter image description here

Can someone give me some pointers? I would greatly appreciate it.

for a further look into my configs, this is my experimental GitHub account for this project. https://github.com/melvinalmonte/testing-practice

Thanks!!

0

1 Answer 1

1

As the error message is shown, react hooks(useState etc..) should be called inside of the react component. But in your test, you called it outside of the react component.

The recommended way to test this case is: Don't test custom hooks directly, test it with react component together.

E.g. app.js:

import React from 'react';
import { CounterHandler } from './counterHandler';

function App() {
  const counter = CounterHandler(0);

  return (
    <div className="App">
      <button name="increase" onClick={counter.increase}>
        click to increase
      </button>
      <button name="decrease" onClick={counter.decrease}>
        click to decrease
      </button>
      <h1>counter: {counter.counter}</h1>
    </div>
  );
}

export default App;

counterHandler.js:

import React from 'react';

export const CounterHandler = (num) => {
  const [counter, setCounter] = React.useState(num);
  const increase = () => setCounter(counter + 1);
  const decrease = () => setCounter(counter - 1);

  return {
    counter,
    increase,
    decrease,
  };
};

app.test.js:

import React from 'react';
import { shallow } from 'enzyme';
import App from './app';

describe('60158977', () => {
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(<App></App>);
  });
  it('should render', () => {
    expect(wrapper.find('h1').text()).toBe('counter: 0');
  });

  it('should increase counter', () => {
    wrapper.find('button[name="increase"]').simulate('click');
    expect(wrapper.find('h1').text()).toBe('counter: 1');
  });

  it('should decrease counter', () => {
    wrapper.find('button[name="decrease"]').simulate('click');
    expect(wrapper.find('h1').text()).toBe('counter: -1');
  });
});

Unit test results with 100% coverage:

 PASS  stackoverflow/60158977/app.test.js
  60158977
    ✓ should render (9ms)
    ✓ should increase counter (3ms)
    ✓ should decrease counter (1ms)

-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |     100 |      100 |     100 |     100 |                   
 app.js            |     100 |      100 |     100 |     100 |                   
 counterHandler.js |     100 |      100 |     100 |     100 |                   
-------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        3.884s, estimated 4s

Source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60158977

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your help! This was what I was looking for. Another question, just to make sure I’m understanding this correct. Because we’re not testing plain JS but react itself I would need to test my component not just my individual function right? I cannot isolate my handler as it can be tested as a whole (almost like integration style).
I want to know how to test for the object returned by the countHandler hooks. By testing the object, i mean testing the shape and values returned within the object.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.