Git Product home page Git Product logo

react-hooks-testing-library's Introduction

react-hooks-testing-library

ram

Simple and complete React hooks testing utilities that encourage good testing practices.


Read The Docs

Build Status codecov version downloads MIT License

All Contributors PRs Welcome Code of Conduct Netlify Status Discord

Watch on GitHub Star on GitHub Tweet

A Note about React 18 Support

If you are using the current version of react-testing-library, replace

import { renderHook } from '@testing-library/react-hooks'

with

import { renderHook } from '@testing-library/react'

Once replaced, @testing-library/react-hooks can be uninstalled.

Details

As part of the changes for React 18, it has been decided that the renderHook API provided by this library will instead be included as official additions to both react-testing-library (PR) and react-native-testing-library (PR) with the intention being to provide a more cohesive and consistent implementation for our users.

Please be patient as we finalise these changes in the respective testing libraries. In the mean time you can install @testing-library/react@^13.1

Table of Contents

The problem

You're writing an awesome custom hook and you want to test it, but as soon as you call it you see the following error:

Invariant Violation: Hooks can only be called inside the body of a function component.

You don't really want to write a component solely for testing this hook and have to work out how you were going to trigger all the various ways the hook can be updated, especially given the complexities of how you've wired the whole thing together.

The solution

The react-hooks-testing-library allows you to create a simple test harness for React hooks that handles running them within the body of a function component, as well as providing various useful utility functions for updating the inputs and retrieving the outputs of your amazing custom hook. This library aims to provide a testing experience as close as possible to natively using your hook from within a real component.

Using this library, you do not have to concern yourself with how to construct, render or interact with the react component in order to test your hook. You can just use the hook directly and assert the results.

When to use this library

  1. You're writing a library with one or more custom hooks that are not directly tied to a component
  2. You have a complex hook that is difficult to test through component interactions

When not to use this library

  1. Your hook is defined alongside a component and is only used there
  2. Your hook is easy to test by just testing the components using it

Example

useCounter.js

import { useState, useCallback } from 'react'

function useCounter() {
  const [count, setCount] = useState(0)

  const increment = useCallback(() => setCount((x) => x + 1), [])

  return { count, increment }
}

export default useCounter

useCounter.test.js

import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from './useCounter'

test('should increment counter', () => {
  const { result } = renderHook(() => useCounter())

  act(() => {
    result.current.increment()
  })

  expect(result.current.count).toBe(1)
})

More advanced usage can be found in the documentation.

Installation

npm install --save-dev @testing-library/react-hooks

Peer Dependencies

react-hooks-testing-library does not come bundled with a version of react to allow you to install the specific version you want to test against. It also does not come installed with a specific renderer, we currently support react-test-renderer and react-dom. You only need to install one of them, however, if you do have both installed, we will use react-test-renderer as the default. For more information see the installation docs. Generally, the installed versions for react and the selected renderer should have matching versions:

npm install react@^16.9.0
npm install --save-dev react-test-renderer@^16.9.0

NOTE: The minimum supported version of react, react-test-renderer and react-dom is ^16.9.0.

API

See the API reference.

Contributors

Thanks goes to these wonderful people (emoji key):


Michael Peyper

πŸ’» πŸ“– πŸ€” πŸš‡ 🚧 πŸ’¬ ⚠️

otofu-square

πŸ’»

Patrick P. Henley

πŸ€” πŸ‘€

Matheus Marques

πŸ’»

Dhruv Patel

πŸ› πŸ‘€

Nathaniel Tucker

πŸ› πŸ‘€

Sergei Grishchenko

πŸ’» πŸ“– πŸ€”

Josep M Sobrepere

πŸ“–

Marcel Tinner

πŸ“–

Daniel K.

πŸ› πŸ’»

Vince Malone

πŸ’»

Sebastian Weber

πŸ“

Christian Gill

πŸ“–

JavaScript Joe

βœ… ⚠️

Sarah Dayan

πŸ“¦

Roman Gusev

πŸ“–

Adam Seckel

πŸ’»

keiya sasaki

⚠️

Hu Chen

πŸ’» πŸ“– πŸ’‘

Josh

πŸ“– πŸ’¬ πŸ’» πŸ€” 🚧 ⚠️

Na'aman Hirschfeld

πŸ’»

Braydon Hall

πŸ’»

Jacob M-G Evans

πŸ’» ⚠️

Tiger Abrodi

πŸ’» ⚠️

Amr A.Mohammed

πŸ’» ⚠️

Juhana Jauhiainen

πŸ’»

Jens Meindertsma

πŸ’» ⚠️

Marco Moretti

πŸš‡

Martin V.

πŸ“–

Erozak

πŸ“–

Nick McCurdy

🚧

Arya

πŸ“–

numb86

πŸ“–

Alex Young

🚧

Ben Lambert

πŸ“–

David Cho-Lerat

πŸ“–

Evan Harmon

πŸ“–

Jason Brown

πŸ“–

KahWee Teng

πŸ“–

Leonid Shagabutdinov

πŸ“–

Levi Butcher

πŸ“–

Michele Settepani

πŸ“–

Sam

πŸ“–

Tanay Pratap

πŸ“–

Tom Rees-Herdman

πŸ“–

iqbal125

πŸ“–

cliffzhaobupt

🚧

Jon Koops

πŸ’»

Jonathan Peyper

πŸ‘€ πŸ’»

Sean Baines

πŸ“–

Mikhail Vasin

πŸ“–

Aleksandar Grbic

πŸ“–

Jonathan Holmes

πŸ’»

MichaΓ«l De Boey

🚧

Anton Zinovyev

πŸ› πŸ’»

marianna-exelate

πŸš‡

Matan Borenkraout

🚧

andyrooger

πŸ’»

Bryan Wain

πŸ› πŸ‘€

Robert Snow

⚠️

Chris Chen

⚠️

Masious

πŸ“–

Laishuxin

πŸ“–

This project follows the all-contributors specification. Contributions of any kind welcome!

Issues

Looking to contribute? Look for the Good First Issue label.

πŸ› Bugs

Please file an issue for bugs, missing documentation, or unexpected behavior.

See Bugs

πŸ’‘ Feature Requests

Please file an issue to suggest new features. Vote on feature requests by adding a πŸ‘. This helps maintainers prioritize what to work on.

See Feature Requests

❓ Questions

For questions related to using the library, you can raise issue here, or visit a support community:

LICENSE

MIT

react-hooks-testing-library's People

Contributors

7michele7 avatar adamseckel avatar agjs avatar allcontributors[bot] avatar aryyya avatar dependabot[bot] avatar doppelmutzi-netpioneer avatar erozak avatar gillchristian avatar goldziher avatar joshuaellis avatar jsjoeio avatar keiya01 avatar levibutcher avatar lukaselmer avatar marcosvega91 avatar marquesm91 avatar masious avatar michaeldeboey avatar mpeyper avatar mtinner avatar mvasin avatar ndresx avatar nickmccurdy avatar ntucker avatar numb86 avatar otofu-square avatar renovate-bot avatar sgrishchenko avatar shagabutdinov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-hooks-testing-library's Issues

How do I test a hook that relies on a closure?

What is your question:

How do I test a hook that relies on a closure over some state? Let's say I have some code that looks like this:

const useUsers = () => {

  const [users, setUsers] = useState([]);
  const [selectedUser, setSelectedUser] = useState();

  const onSelectUser = useCallback((event) => {
	const { value : userId } = event.target;

	const selectedUser = users.filter(user => user.id === userId)[0];
	setSelectedUser(selectedUser);
  }, [users]);

  return {
    users,
    setUsers,
    onSelectUser
  };

}

For my unit test, I want to make sure that the selectedUser is set whenonSelectUser is called. Because onSelectUser iterates over the users array held in state I need this closure to be maintained.

My test looks like this

const users = [{id: 1, name: 'alice'}, {id:2, name:'bob'}];
const event = { target: { value: 2 }};

const { result } = renderHook(() => useUsers());

const { onSelectUser, setUser } = result.current;

act(() => {
        // trying to set the users array here so onSelectUser will have closure over it
	setUsers(users);

       // whenever this function runs the users array is undefined
	onSelectUser(event);
});

// fails as result.current.selectedUser is undefined
expect(result.current.selectedUser).toBe(users[1]);

Is there something I can do to get this to work? Or is the library not intended to be used this way?

Testing custom hook throwing error for incorrect arguments

  • react-hooks-testing-library version: 0.3.7
  • react-testing-library version: N/A (I haven't installed this package in my repo for testing)
  • react version: 16.8
  • node version: 8.11.2
  • npm (or yarn) version: 5.6.0

Relevant code or config:

const usCustomHook = (argA, argB) => {
  if (!argA || typeof argA !== 'function') {
    throw new Error('argA must be a function');
  }

  if (!argB || typeof argB !== 'string' || !argB.length) {
    throw new Error('argB must be a string');
  }

  ..... // code for the custom hook
}

What you did:

I am trying to implement a custom hook adding validation for the arguments to match the expectations. undefined arguments should result in throwing error.

What happened:

The custom hook itself works as expected, however when testing above using jest passes tests but prints errors in console:

Tests:

test('Not providing valid argA should fail', () => {
    try {
      renderHook(() => useCustomHook(undefined, 'invalid data'));
    } catch (e) {
      expect(e.message).toBe('argA must be a function');
    }

    try {
      renderHook(() => useCustomHook('foo', 'invalid data'));
    } catch (e) {
      expect(e.message).toBe('argA must be a function');
    }
  });

  test('Not providing valid argB should fail', () => {
    try {
      renderHook(() => useCustomHook(() => {}, undefined));
    } catch (e) {
      expect(e.message).toBe('argB must be a string');
    }

    try {
      renderHook(() => useCustomHook(() => {}, 12345));
    } catch (e) {
      expect(e.message).toBe('argB must be a string');
    }
  });

Error it prints in console with tests passing:

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Uncaught [Error: argA must be a function]

console.error node_modules/react-dom/cjs/react-dom.development.js:17071
    The above error occurred in the <TestHook> component:
        in TestHook
    
    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Uncaught [Error: argA must be a function]

console.error node_modules/react-dom/cjs/react-dom.development.js:17071
    The above error occurred in the <TestHook> component:
        in TestHook
    
    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Uncaught [Error: argB must be a string]

console.error node_modules/react-dom/cjs/react-dom.development.js:17071
    The above error occurred in the <TestHook> component:
        in TestHook
    
    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
    Error: Uncaught [Error: argB must be a string]

console.error node_modules/react-dom/cjs/react-dom.development.js:17071
    The above error occurred in the <TestHook> component:
        in TestHook
    
    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

Reproduction:

CodeSandBox link: https://codesandbox.io/s/183om7054

Problem description:

I believe it would be ideal not to print the jsdom errors since the errors are catched and error message is verified using jest.

CHANGELOG missing

I just saw the package is updated to v0.3.3, but I can't find any CHANGELOG to check out the changes.

Would you be so kind to provide us with a decent CHANGELOG please? πŸ™‚

Thanks for all your hard work @mpeyper! πŸ™‚

rerender more than once?

Hey, just started using this library and I see it's fairly new (only 2 tagged questions on SO and no dedicated room in Reactiflux). I guess there are 3 questions now because of me :)

https://stackoverflow.com/questions/56175396/why-is-re-run-only-updating-props-one-time-with-react-hooks-testing-libary

I posted this question, assuming the props should update on each rerender call. However I'm not sure if it's supposed to do that and it's a bug, or it's not supposed to do that and the documentation should be clarified. Thanks for any guidance!

Uses deprecated package name react-testing-library as dependency over @testing-library/react

  • react-hooks-testing-library version: 1.1.0
  • react-test-renderer version: 8.0.5 (@testing-library/react)
  • react version: 16.8.6
  • node version: 12
  • npm (or yarn) version: 6.9 (npm)

Relevant code or config:

import { renderHook, act } from '@testing-library/react-hooks';

What you did:

Was writing tests for custom hooks

What happened:

Cannot find module 'react-test-renderer' from 'index.js'

1 | import { renderHook, act } from '@testing-library/react-hooks';

Reproduction:

Create a react app and install @testing-library/react and @testing-library/react-hooks, and observe that @testing-library/react-hooks seems to require the old package name, react-testing-library

Problem description:

I want to use the latest versions of everything, and not the deprecated package name

Suggested solution:

Change all instances of react-testing-library with @testing-library/react

Curious why setCount being called in the useCallback() hook.

What is your question:

I was reading the docs and confused why is setCount being called in the useCallback() hook.

const [count, setCount] = useState(0)
const increment = useCallback(() => setCount((x) => x + 1), [])
return { count, increment }

I read this article by kent c dodds that explains calling setState in the useCallback hook is not effective. I was wondering if there was a reason related to this that Im not getting.

https://kentcdodds.com/blog/usememo-and-usecallback

Im just trying to learn thank you.

v1.0.0 release

I'd like to release a v1.0.0 version (assuming no issues come out of v0.4.0). I hate when libraries remain on alpha versions forever and the main API has not changed since the v0.3.0 changes shortly after hooks were released.

If there is anything anyone think needs to be done before goin out of alpha versions, now is your chance to speak up. I would have like better documentation (#19) but I don't have the time just at the moment (moving interstate), so unless someone wants to take that on it will have to wait until after.

I'll give some time for the dust to settle v0.4.0 release (about a week?) and for people to see and comment on this issue, then push the new version out.

How to turn off nighttime mode in the docs

A way to turn off the nighttime mode. Perhaps an option to switch to a lighter background color. Darker background makes it very hard to read the text, especailly if you are using flux like I am.

Describe the feature you'd like:

Suggested implementation:

Describe alternatives you've considered:

Teachability, Documentation, Adoption, Migration Strategy:

Cannot find module 'react-test-renderer'

What is your question:

I'm getting an error Cannot find module 'react-test-renderer' from 'index.js' when I am executing my tests.
Currently I don't have react-test-renderer listed in my package.json. But I saw it as a peer dependency in the package.json of react-hooks-testing-library.

Do I have to install the peerDependency by my own?
I'm using v0.5

Testing custom hook and getting "Warning: An update to TestHook inside a test was not wrapped in act...

I've implemented a simple custom hook in order to debounce a value update.

Here is the code:

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

The client of this hook is something like this (very simplified):

function Search(props) {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  // doing something useful with debouncedSearchTerm....
  // ...
  // ...
}

So, I'm trying to test the hook with the code below:

import { renderHook, act } from 'react-hooks-testing-library';
import useDebounce from '../useDebounce';

jest.useFakeTimers();

it.only('should update value after specified delay', () => {
  const { result, rerender } = renderHook(
    ({ value, delay }) => useDebounce(value, delay),
    { initialProps: { value: '', delay: 500 } }
  );

  expect(result.current).toBe('');
  jest.advanceTimersByTime(510);
  expect(result.current).toBe('');

  rerender({ value: 'Hello World', delay: 500 });

  expect(result.current).toBe('');
  jest.advanceTimersByTime(498);
  expect(result.current).toBe('');
  jest.advanceTimersByTime(3);
  expect(result.current).toBe('Hello World');
});

Although, the test passes, I get the following warning:

console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:102

Warning: An update to TestHook inside a test was not wrapped in act(...).        When testing, code that causes React state updates should be wrapped into act(...):        act(() => {      /* fire events that update state */    });    /* assert on the output */        This ensures that you're testing the behavior the user would see in the browser. Learn more at https://fb.me/react-wrap-tests-with-act        in TestHook        in Suspense

I get that if I'm about to call a function in order to update the internal state of the hook (e.g. increment() of a useCounter hook), I have to do it in an act function, as the documentation instructs.

But, the useDebounce hook I've implemented changes state by an internal useEffect that runs whenever value or delay change.

How can I get rid of this warning? Is it something wrong with my code? Am I forgetting to add something in the test code?

Please help!

TypeError: Cannot read property 'current' of undefined

  • @testing-library/react-hooks version: 2.0.1
  • react-test-renderer version: 16.8.6
  • react version: 16.8.6
  • node version: v12.9.0
  • npm version: 6.10.2

Relevant code or config:

import { renderHook } from '@testing-library/react-hooks';
import { useMyHook } from '../useMyHook';

test('useMyHook initializes correctly', () => {
  const { result } = renderHook(() => useMyHook());
});

What you did:

simply run the tests react-scripts test --env=jsdom --watchAll

What happened:

It throw an error saying it can not read property 'current'. I searched most places and I didn't see anyone else getting the same error.

TypeError: Cannot read property 'current' of undefined

      12 | test('useMyHook initializes correctly', () => {
    > 13 |   const { result } = renderHook(() => useMyHook());
                                ^

Reproduction:

https://codesandbox.io/s/kind-agnesi-v9ren

Problem description:

Because it doesn't seem to be possible to test hooks with this library.
Is there something missing in the docs that needs to be done as an extra step?

await act makes test fail

  • react-hooks-testing-library version: 0.5.1
  • react-test-renderer version: 16.9.0
  • react version: 16.9.0
  • node version: 12.2.0
  • npm (or yarn) version: 6.9.0

Relevant code or config:

hooks.js

const initialState = {
  requesting: false,
};

export function reducer(state, action = {}) {
  switch (action.type) {
    case DATA_PATCH_REQUEST:
      return { ...initialState, requesting: true };
    default:
      return state;
  }
}

const requestAction = { type: DATA_PATCH_REQUEST };

function useManageDataUpdate() {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  function dataChangeHandler(newValue) {
    patchChanges(newValue);
  }

  function patchChanges(value) {
    dispatch(requestAction);
    // request 
  }

  return { state, dataChangeHandler };
}

hooks.test.js

it('should change requesting to true when dataChangeHandle is called', () => {
  const { result } = renderHook(() => useManageDataUpdate());

  expect(result.current.state.requesting).toEqual(false);

  act(() => {
    result.current.dataChangeHandler('abc');
  });

  expect(result.current.state.requesting).toEqual(true);
});

What you did:

  • updated react to 16.9
    changed act() to await act()

ran the test

What happened:

expect(received).toEqual(expected) // deep equality

Expected: true
Received: false

  78 | 
  79 |         console.log('end');
> 80 |         expect(result.current.state.requesting).toEqual(true);
     |                                                 ^
  81 |       });
  82 |     });
  83 |   });

Reproduction:

Problem description:

After updating the react, react-dom and react-test-renderer to 16.9.0 and changed act() which with any async code was producing nasty warning to await act() tests are no more passing. The state which should change stays the same

Suggested solution:

Invariant Violation when testing hook that uses npm-imported hook

I'm trying to test my custom hook that is based on another hook imported from an npm package (useConnect).

So the call chain is: useMyCustomHook -> calling useConnect -> calling stock react hooks

When calling this in a React component in browser it works great.

When running inside renderHook test harness (with jest) it immediately throws an "Invariant Violation: Hooks can only be called inside the body of a function component". It is raised from inside useConnect when it tries to invoke it's first hook.

When I don't import useConnect and just copy-paste it's source code (so it's transformed by jest's babel rather than imported from a "production" build of the package) – again it works flawlessly.

What am I missing?

How to write assertions about an element rendered by a hook?

Among other things, my hook is rendering a React element, e.g.

const useFilters = (userConfiguration) => {
  const filtersElement = <div>Hello, World!</div>;

  return {
    filtersElement
  };
};

How do I write assertions about the rendered filtersElement element?, i.e.

// @flow

import React from 'react';
import {
  renderHook
} from 'react-hooks-testing-library';
import useFilters from './useFilters';

it('renders Hello, World!', () => {
  const {result} = renderHook(() => {
    return useFilters();
  });

  // How to get to something equivalent to:
  // expect(result.current.filtersElement.contains(<div>Hello, World!</div>)).toEqual(true);
});

Prevent `act` warning on async hook

Currently, there is a warning about updating a hook outside of an act call when the update happens as the result of an async call (i.e. callback, promise, async/await, etc.)

There is an example of this in our own test suite which generates the warning when we run our tests.

Firstly I'd like this warning to go away. I've tried all the suggestions in @threepointone's examples repo to no avail. There has also been a rather lengthy discussion in the react-testing-library repo on the topic.

Secondly, if there is a way to remove the warning, the solution(s) should be documented in our docs to help others.

Finally, is there anything we could be doing/wrapping in our API so that users do not have to call act in their own code as often, similarly to the work that has already been done in react-testing-library?

I'm not 100% certain on whether there is anything we can actually do, so if you're more familiar with the act function and its uses, I'd love to get your input.

Incorrest state when testing with renderHook

I was trying to test behavior of the useAsync hook using the renderHook from the testing-library/react-hooks, and found the following issues (which is probably I'm trying to use the library in a wrong way).

First, when hook returns it's initial state, it returns initialValue (as expected), but it also indicates that the promise has been already resolved:

it('should set into proper initial state', async () => {
    const asyncFunc = () => Promise.resolve(2);

    const { result } = renderHook(() => useAsync(asyncFunc, { initialValue: 1 }));

    expect(result.current.value).toEqual(1);
    expect(result.current.isResolved).toBe(false); // FAILS, ALREADY RESOLVED
});

I expected it to be either { data: 1, isResolved: false }, or { data: 2, isResolved: true }.

Second issue, is that for some reason the waitForNextUpdate never resolve:

it('should resolve resolved promise', async () => {
    // already resolved
    const asyncFunc = () => Promise.resolve(2);

    const { result, waitForNextUpdate } = renderHook(() => useAsync(asyncFunc, { initialValue: 1 }));

    expect(result.current.value).toEqual(1);

    await waitForNextUpdate(); // NEVER RESOLVE
});

it('should resolve asynchronous promise', async () => {
    // will resolve in 50ms
    const asyncFunc = () => new Promise(resolve => setTimeout(resolve, 50)).then(() => 2);

    const { result, waitForNextUpdate } = renderHook(() => useAsync(asyncFunc, { initialValue: 1 }));

    expect(result.current.value).toEqual(1);

    await waitForNextUpdate(); // NEVER RESOLVE
});

This is probably related to the first issue - promise is in resolved state already but the value from the promise wasn't set.

AppendChild error occurring on all calls of RenderHook

What is your question:

Hey Team, I'm having trouble running some of the simplest tests available in your repo. I keep receiving the following error after running the useState hook example:

  1) #useContainer passes a function called callApi to the base component:
     TypeError: Cannot read property 'appendChild' of undefined
      at render (node_modules/react-testing-library/dist/index.js:58:31)
      at renderHook (node_modules/react-hooks-testing-library/lib/index.js:71:49)
      at Context.<anonymous> (app/bundles/tests/common/hooks/useContainer.test.js:37:24)

I'm haven't been able to deduce why this is occurring - does the library require a specific configuration with Babel potentially?

Here is my code:

import { useState } from 'react';
import { renderHook, cleanup, act } from 'react-hooks-testing-library';
import {
  describe,
  it,
  beforeEach,
  expect,
} from 'mocha';

describe('useState', () => {
  afterEach(cleanup);
  it('passes a function called callApi to the base component', () => {
    const { result } = renderHook(() => useState('foo'));
    const [value] = result.current;
    expect(value).toBe('foo');
  });
});

Is Rtl.fireEvent() compatible with react hooks testing library?

I have just updated to new versions of react and testing-library and now I see this warning in my hook's test file:

 Warning: It looks like you're using the wrong act() around your test interactions.
      Be sure to use the matching version of act() corresponding to your renderer:

      // for react-dom:
      import {act} from 'react-dom/test-utils';
      //...
      act(() => ...);

      // for react-test-renderer:
      import TestRenderer from 'react-test-renderer';
      const {act} = TestRenderer;
      //...
      act(() => ...);
          in TestHook
          in Suspense

Here is code of my test:

import { fireEvent } from '@testing-library/react';
import { renderHook, act } from '@testing-library/react-hooks';

// ...

it('should update return value on hover', () => {
    jest.useFakeTimers();

    const ref = createRefMock();
    const { result } = renderHook(() => useHover(ref));

    expect(result.current).toBe(false);

    fireEvent.mouseOver(ref.current);

    act(() => {
        jest.runOnlyPendingTimers();
    });

    expect(result.current).toBe(true);

    fireEvent.mouseOut(ref.current);

    expect(result.current).toBe(false);
});

Warning appears on line fireEvent.mouseOut(ref.current);

And if I change it to

act(() => {
     fireEvent.mouseOut(ref.current);
});

everything is working fine.

But is still strange that I don't have any problems with fireEvent.mouseOver.

Do I need to wrap every fireEvent to act in my code?

πŸ“£ RFC: Alternative renderHook APIs

I'm working on the improved docs in #19 and I'm struggling a bit with the section on changing the parameters passed to the hooks. Essentially, my issue is that there are 2 ways you can do it.

The first way is to just keep local variables and update them before rerendering:

let someValue = 0
const { result, rerender } = renderHook(() => useSomeHook(someValue))

// update value
someValue = 1
rerender()

The second way is the use the options.initialProps and rerender(newProps) features:

const { result, rerender } = renderHook(({ someValue }) => useSomeHook(someValue), {
  initialProps: { someValue: 0 }
})

// update value
rerender({ someValue: 1 })

The main reason the second way exists was to work around an issue in our own unit test for ensuring useEffect hooks were testable. The problem was that the updated value was updated for the cleanup of the effect too rather than capturing the initial value. In hindsight, I can see that is probably only an issue for that test, as most users would be passing the values into a hook function, capturing the initial value for the cleanup.

I'm not saying that having the callback function scope the variables isn't still a good idea in some cases, but I think it's a non-issue for most users.

Now we could just remove the initialProps/newProps functionality altogether, but I personally find the usage of a bit more ergonomic, especially when there is more than 1 value to update, as I don't have to keep a variable and spend multiple lines to rerender (1 for each update to the values and the another to render), so I'm asking for opinions on people's preferred API for this.

I'm also open to any ideas for a better API than either of these if you can think of one. I've actually been playing with an idea for a "hook-like" API along the lines of:

const { result, updateProps } = renderHook(() => {
  const someValue = useProps(0)
  return useSomeHook(someValue)
})

// update value
updateProps(1)

Where updateProps calls rerender under the hood. I'm not sure if this is a good idea, just a thought I had. Anyway, let me know your thoughts on any of the above.

Can rerender avoid unmounting?

rerender currently seems to always unmount the hook, causing the useEffect cleanup hooks to be called etc. This means that it is not easy to test repeated invocations of a hook.

How to test useReducer dispatch that errors?

What is your question:

How can I test that a reducer dispatch results in an error?

The renderHook api returns a result object with an error key. The docs state that this is an error that is thrown if the callback function threw an error during rendering. I'm trying to test for an error from a subsequent dispatch, not from the initial renderHook call.

Dan Abrmaov suggests that with useReducer, the default switch case should throw an error: https://twitter.com/dan_abramov/status/1093969005675773954?lang=en

import { useReducer } from 'react';
import { act, renderHook } from 'react-hooks-testing-library';

const initialState = {};

function reducer(state, action) {
  switch (action.type) {
    default: 
      throw new Error('Unknown action type');
  }
}

test('throws an error when dispatched with an unknown action type', () => {
  expect.assertions(1);

  const { result } = renderHook(() => useReducer(reducer, initialState));
  const [, dispatch] = result.current;

  try {
    act(() => {
      dispatch({ type: 'whaaat' });
    });
  } catch (e) {
    // asserting here does not appear to be the right approach
  }
});

Would the best approach be to render the test by wrapping it in an ErrorBoundary type component and testing for the resulting render?

As it stands, I get the following reported, just not sure how to catch/handle it in my test.

Screen Shot 2019-04-17 at 9 37 10 AM
Screen Shot 2019-04-17 at 9 37 06 AM

Improve documentation

The example in the README is very basic (by design) but there have been numerous queries recently about more advanced usage so I think it's time we expand the documentation to cover these.

Initially, this could be part of the README, but I'd prefer keeping the README fairly light and having a docs directory that can explain features, concepts and techniques in more details (a .md file per topic).

We could look at generating a gitbook, docusaurus, docz or whatever the documentation site flavour of the month is from these docs at some point in the future (unless anyone want to set this up now).

Another thing that I am aware of (from experience 😞) is that docs written by the author are often not very useful to users (especially new users), as there is so much implied understanding that gets baked in. That's why I'm asking for help for others to write these docs. Include any gotchas or hiccups you hit along the way.

It doesn't have to be completely comprehensive from the get-go and we can improve and expand them as we go, so any and all contributions are welcome towards them.

Topics to cover:

  • when you should use this library
  • when you should NOT use this library
  • usage of wrapper (renderHook option)
  • usage of initialProps (renderHook option) and the newProps (rerender parameter)
    • dynamic props
    • memoization
  • usage of waitForNextUpdate
  • error handling
  • recipies
    • common hook patterns (if there are any?)
    • setup with different test runners
  • examples
  • troubleshooting
  • FAQ
  • API

There probably more that can also be included so feel free to go with want makes sense to you. I'm happy to update the checklist if you want to make suggestions in the comments here.

Note: I'm marking these off as we go, but that does not mean what is shown in the work-in-progress site as at all the final version and/or closed to improvements.

Having trouble testing useEffect/useState in hook

I'm having trouble making the following test pass and I was hoping you can help me.

import { useEffect, useState } from "react";

export function useComponentResources(required) {
  const [componentResources, setComponentResources] = useState(null);

  useEffect(() => {
    if (required) {
      // api call
      setTimeout(() => setComponentResources({}), 100);
    }
  }, [required]);

  return componentResources;
}
import { renderHook } from "@testing-library/react-hooks";
import { useComponentResources } from "./component-resources.hook";

describe("component-resources.hook", () => {
  it("fetches resources when required", () => {
    //act
    const { result } = renderHook(() => useComponentResources(true));

    //assert
    expect(result.current).toEqual({});
  });
});

It keeps failing:

expect(received).toEqual(expected)

Expected value to equal:
  {}
Received:
  null

Difference:

  Comparing two different types of values. Expected object but received null.

   7 |     const { result } = renderHook(() => useComponentResources(true));
   9 |     //assert
> 10 |     expect(result.current).toEqual({});
  11 |   });
  12 | });

I have created a repro case in codesandbox. Hopefully it makes it easier point out what I'm doing wrong:

https://codesandbox.io/embed/priceless-browser-94ec2

Global `unmountAll` functionality

I have an effect that uses setInterval. In order for my tests to work right, I need to unmount so the interval gets knocked away.

I have code like this:

let globalUnmount;

// I use this is all of my tests to run render the hook
async function runEffects(wait = true) {
  const { result, unmount } = renderHook(() =>
    useMyHookWithIntervals()
  );

  globalUnmount = unmount;
  return {
    result
  };
}
afterEach(() => {
  if (globalUnmount) {
    globalUnmount();
    globalUnmount = null;
  }
});

Is this the expected paradigm? Is this an example that would be good for docs? Is this horrible?

Get how many times the component was rendered

Describe the feature you'd like:

I would like to get inside result a property with number of times the component was rendered. It could be called renderCount.

The only drawback I can think about is performance issues, but I don't think a counter is a problem.

Suggested implementation:

One option could be wrapping the setValue passed to TestHook so it increase an external count each time than setValue is called.

Another option could be to pass an utils object to TestHook as a children with properties setValue and increaseCount so TestHook itself is in charge of increasing the count before calling children.setValue.

I think it's important increasing the count even if it fails, as in the end the component was triggered to be rendered.

Not really sure about where to init the count. Maybe a ref inside TestHook?

Describe alternatives you've considered:

I'm trying to test a hook that returns a function to force a component to be re-rendered. I've tried to pass the count from outside but 1) I can't pass inner components, only outer (the wrapper) or 2) pass component prop but this is not possible.

I could build my own testing component with react-testing-library that calls the function returned by the hook when a button is clicked or something like that but I only have this library available in the project for now and I don't want to install RTL only for this test.

I can't think other way to test the hook I described.

Teachability, Documentation, Adoption, Migration Strategy:

I think is useful to get how many times the component was rendered for other purposes too: checking performance issues or memoized behaviors.

Would be easy to include in the docs, just adding a new element to renderHook Result section explaining what do you get for renderCount property.

This library doesn't seem to work with custom hooks that return arrays ?

  • react-hooks-testing-library version: 1.0.4
  • react-test-renderer version: 16.8.6
  • react version:16.8.6

I have this custom hook that returns an array:

export const useEmailAddressState = () => {
  const [email, setEmail] = useState('')
  const validateEmail = emailz => {
    var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(String(emailz).toLowerCase());
  }
  let isInvalid = false
  if(email === null || email === "") {
    return [email, isInvalid, setEmail]
  }
  isInvalid = ! validateEmail(email)
  return [email, isInvalid, setEmail]
}

And It seens that act(() => ...) is not updating the state, the second expect is failing,
and the value of email is unchanged.

   test('useEmailAddressState', done => {
    const {result} = renderHook(() => useEmailAddressState())
    let [email, isInvalid, setEmail] = result.current
    expect(email).toEqual('')
    act(() => {
      setEmail("pwerofw0")
    })
    [email, isInvalid, setEmail] = result.current
    expect(isInvalid).toEqual(true)
    done()
   })



With React ^16.9.0 - Testing a UseEffect making an async call to an s3.getSignedUrl service

Hi hackers,

Not quite sure if I am doing the right thing, but I am hitting this failed test:

image

Component:

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import ReactPlayer from 'react-player';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
import DragIndicator from '@material-ui/icons/DragIndicator';
import Edit from '@material-ui/icons/Edit';
import Delete from '@material-ui/icons/Delete';
import CircularProgress from '@material-ui/core/CircularProgress';
import AWS from 'aws-sdk';

import styles from './VideoOutlineElement.style';

const credentials = {
  accessKeyId: process.env.REACT_APP_ACCESS_KEY_ID,
  secretAccessKey: process.env.REACT_APP_SECRET_ACCESS_KEY,
};

AWS.config.update({
  credentials,
  region: 'us-east-1',
});

const s3 = new AWS.S3();


const VideoOutlineElement = ({
  title,
  details,
  videoSource,
}) => {
  const classes = styles();
  const [signedUrl, setSignedUrl] = useState('');

  useEffect(() => {
    const getSignedObject = async () => {
      const signedRequest = await s3.getSignedUrl('getObject', {
        Bucket: 'scb-video-source-development', // Alternative: `scb-video-source-${process.env.REACT_APP_STAGE}`
        Key: videoSource.key,
      });

      setSignedUrl(signedRequest);
    };

    getSignedObject();
  }, [videoSource]);

  return (
    <div>
      <div className={classes.videoDescription}>
        <div className={classes.dragIndicator}>
          <IconButton
            aria-label="Move Element"
            className={classes.dragIndicatorIcon}
          >
            <DragIndicator />
          </IconButton>
        </div>
        <div className={classes.subHeader}>
          <Typography
            variant="subtitle1"
            className={classes.title}
          >
            {title}
          </Typography>
        </div>
        <div className={classes.actions}>
          <IconButton
            aria-label="Edit Element"
            className={classes.actionIcons}
          >
            <Delete />
          </IconButton>
          <IconButton
            aria-label="Delete Element"
            className={classes.actionIcons}
          >
            <Edit />
          </IconButton>
        </div>
      </div>
      <div
        className={classes.videoPlayer}
      >
        {
          signedUrl && (
            <ReactPlayer
              url={signedUrl}
              controls
              playing={false}
              width="100wv"
              height="100hv"
            />
          )
        }
        {
          !signedUrl && (<CircularProgress size={24} />)
        }
      </div>
      <div>
        <Typography
          variant="subtitle2"
          className={classes.details}
        >
          {details}
        </Typography>
      </div>
    </div>
  );
};

VideoOutlineElement.defaultProps = {
  title: '',
  details: '',
  videoSource: {
    url: '',
    key: '',
    contentType: '',
  },
};

VideoOutlineElement.propTypes = {
  title: PropTypes.string,
  details: PropTypes.string,
  videoSource: PropTypes.object,
};

export default VideoOutlineElement;

Test:

import React from 'react';
import { cleanup, render } from '@testing-library/react';
import { renderHook, act } from '@testing-library/react-hooks';

import VideoOutlineElement from './VideoOutlineElement.component';

afterEach(cleanup);

test('VideoOutlineElement - Simulate signedUrl response', () => {
  const s3ObjectUrl = 'f4ba62f7-ee0d-4f2b-b97d-631be258ec21.mp4';

  const props = {
    title: 'test title',
    details: 'test details',
    videoSource: {
      url: s3ObjectUrl,
      key: s3ObjectUrl,
      contentType: 'viceo/mp4',
    },
  };

  const { result } = renderHook(() => VideoOutlineElement(props));

  let [signedUrl, setSignedUrl] = result.current;

  expect(signedUrl).toEqual('');

  act(() => {
    setSignedUrl(s3ObjectUrl);
  });

  [signedUrl, setSignedUrl] = result.current;

  expect(signedUrl).toEqual(s3ObjectUrl);
});

How to avoid waitForNextUpdate Catch-22

It seems I can't properly test async situations, if values are sometimes cached.

Let's say I check a value every minute. I keep it cached for five minutes. So in reality, I am making the async request every five minutes.

I have a helper method to test such things like this:

async function runEffects(wait = true) {
  const { result, waitForNextUpdate } = renderHook(() =>
    useActiveStream()
  );

  if (wait) {
    await waitForNextUpdate();
  }

  return {
    result
  };
}

In practice, I know to pass true for wait on the first try. Then on tries 2,3,4 to pass false. Then back to 5 I would pass true. This is assuming I am trying after running fake timers for a minute each time.

Here is the issue: If an XHR request gets made on the second try, my test will still pass since I am not waiting for update. Nothing will have been called when the code runs without the await. My first thought would be to always await to catch that, but obviously that would leave a promise that times out the test since when it's functional, there is no promise being resolved.

Is there a way around this?

useEffect not run after change value in first useEffect

  • react-hooks-testing-library version: 0.5.1
  • react-test-renderer version: 16.8.6
  • react version: 16.8.6
  • node version: 11.1
  • npm (or yarn) version: 1.15.2

Relevant code or config:

// useHello.js

import { useEffect, useState } from "react";

export default function useTest() {
  const [testUpdated, setTestUpdated] = useState(null);

  useEffect(() => {
    setTestUpdated({});
  }, [setTestUpdated]);

  useEffect(() => {
    console.log("testUpdated updated:", testUpdated);
  }, [testUpdated]);

  return {
    testUpdated
  };
}
// useHello.test.js

import { renderHook } from "react-hooks-testing-library";

import useHello from "../useHello";

describe("useTest", () => {
  it("should be ok", async () => {
    const result = renderHook(useHello).result.current;

    expect(result).toEqual(expect.any(Object));
  });
});

What you did:

I want to track testUpdated change. I change the testUpdated value in first useEffect.

What happened:

It should output testUpdated updated twice, But now only once.

Reproduction:

Codesandbox Link

Problem description:

It works well at 0.4.x version, but when I upgrade to 0.5.x, It's not work.

Suggested solution:

I try to use @testing-library/react to test, And It's work fine.

act() doesn't seem to be updating state

I have a simple custom hook like so:

import { useState } from 'react';

export default function useOpenClose(initial = false) {
	const [isOpen, setOpen] = useState(initial);

	const open = () => { setOpen(true); }
	const close = () => { setOpen(false); }

	return [isOpen, { open, close }];
}

I have a test that's somewhat passing. When I have a test that updates state, it doesn't seem to work.

The test in question:

import { renderHook, act } from '@testing-library/react-hooks';
import useOpenClose from './useOpenClose';

describe('useOpenClose', () => {
    const { result: { current } } = renderHook(() => useOpenClose());
    const [isOpen, { open, close }] = current;

    test('Should have initial value of false', () => {
        expect(isOpen).toBe(false);
    });

    test('Should update value to true', () => {
        act(() => open())
        console.log(isOpen)
    })
});

Test 1 passes, but when I console.log(isOpen) for test 2, isOpen remains false.

Does it have anything to do with the array destructuring? Is it immutable? Or is it the way I'm using act()?

Add docs to testing-library-docs

There is an existing PR created to add docs to testing-library-docs from when testHook was part of react-testing-library. Since it was removed and the code merged with this library, the PR has stalled waiting on updates to change it to reference this library instead.

If anyone wants to take over the PR and update it accordingly, you are more than welcome. You can even add yourself as a contributor here for your effort too.

See the PR for more details.

Disclaimer: I am not affiliated with the testing-library-docs repo or the PR in question, but I was asked to take it over and just find myself a bit time poor to actually do so at the moment.

Why is result a ref(-like)?

Is there a reason why result is a Ref (or ref-like)?

What is the advantage of getting the result off result.current instead of the direct result?

const { result } = renderHook(() => useTheme('light'))
const theme = result.current
// why not...?
const { result: theme } = renderHook(() => useTheme('light'))

It would be a very simple change
https://github.com/mpeyper/react-hooks-testing-library/blob/master/src/index.js#L24

So, I feel like I must be missing something obvious.

Unable to test hooks that Suspend

  • react-hooks-testing-library version: 0.4.0
  • react-testing-library version: 6.0.3
  • react version: 16.8.6
  • node version: 11.10.0
  • npm (or yarn) version: 1.15.2

Relevant code or config:

import { useResource } from 'rest-hooks';
renderHook(() => {
      article = useResource(CoolerArticleResource.singleRequest(), payload);
      return article;
    })

What you did:

Running unit tests on 'rest-hook's library. Trying to update from react-testing-library - which works fine - to react-hooks-testing-library.

(https://github.com/coinbase/rest-hooks/blob/master/src/react-integration/__tests__/integration.tsx)

What happened:

Invariant Violation: Rendered more hooks than during the previous render.

Reproduction:

https://github.com/coinbase/rest-hooks/blob/update-test/src/react-integration/__tests__/integration.tsx#L167
(run yarn test on that branch)

Problem description:

Impossible to test hooks that use suspense. (This was fixed in React 16.8.2 facebook/react#14821)

Suggested solution:

How to use the rerender API ?

Hey guys,

I'm sorry, i'm not very confortable with other testing libraries, and i cannot get how to test my hooks with this library.

I have a very simple hook, which wait for visibility to store a timestamp of render time, which will never update after that :

export function useRenderedAt(isViewable: boolean) {
  const [renderedAt, setRenderedAt] = React.useState(Infinity);

  React.useEffect(() => {
    if (isViewable) {
      setRenderedAt(Date.now());
    }
  }, [isViewable]);

  return renderedAt;
}

I'm trying to test it this way :

  test.only('should return render time when viewable', done => {
    const hook = renderHook(() => useRenderedAt(false), {
      initialProps: false
    });
    expect(hook.result.current).toBe(Infinity);
    hook.waitForNextUpdate().then(() => {
      expect(hook.result.current).not.toBe(Infinity);
      done();
    });
    hook.rerender(true);
  });

What am i doing wrong ?

Merge with testHook - move package here?

Hi,
Some folks that worked on hook test utils in react-testing-library (@kentcdodds, @Donavan) are thinking about extracting testhook into its own package. The API is pretty similar to what you have here - would you consider reviewing the current state of that API and merging it with react-hooks-testing-library?

Discussion: testing-library/react-testing-library#302

WIP Docs for testHook: testing-library/testing-library-docs#32

Next steps:

  • Review and port the API if it is an improvement
  • Add a docs page in the "ecosystem" section of the docs repo
  • Port examples in the docs PR to extracted library

Expecting result.error after "rerender" is not working

  • react-hooks-testing-library version: 0.5.0
  • react-testing-library version: 7.0.0
  • react version: 16.8.6
  • node version: 10.12.0
  • yarn version: 1.13.0

Relevant code or config:

const { result, rerender } = renderHook(
    ({ second }) => {
        const obj = second ? { foo: "brr", baz: "new" } : { foo: "baz" }
        return useAsObservableSource(obj)
    },
    { initialProps: { second: false } }
)
rerender({ second: true })
expect(result.error).toMatchInlineSnapshot(
    `[Error: the shape of objects passed to useAsObservableSource should be stable]`
)

What you did:

I had to edit the following line and removed the state check to make the test passing.

https://github.com/mpeyper/react-hooks-testing-library/blob/934423c3ffc4b6b546a88fce4b79b1179cc27007/src/index.js#L30

What happened:

Without that modification, the result.error after the rerender is undefined

Reproduction:

Failing test: https://travis-ci.org/mobxjs/mobx-react-lite/builds/533269870#L484
Source code: https://github.com/mobxjs/mobx-react-lite/blob/no-observable-source-failing/test/useAsObservableSource.test.tsx#L344

Problem description:

I am not entirely sure what's happening there. The error is caught within componentDidCatch, but the resultContainer.setError is not called at all. It's really strange.

Suggested solution:

I think that for the purpose of testing the children should be always returned from ErrorBoundary. React will remount it anyway, so there is no problem of endless loop. The state.hasError is more useful in real apps where you want to inform a user about the problem and offer some way out of it.

Edit: Later I realized my change has broken other tests, so it's the not correct way either.

Server side rendering

Describe the feature you'd like:

Support for server side rendering. I have rough implementation in the gist below... me than happy to drop this in a PR.

Suggested implementation:

https://gist.github.com/simmo/9b8d9fb13b5fba513a76a8413eeeb805

Describe alternatives you've considered:

I haven't seen any... yet.

Teachability, Documentation, Adoption, Migration Strategy:

I guess an additional export? Happy to take some guidance! πŸ˜„

Async hook testing

Hi, I am wondering how I can test hooks with async behavior. I would like to create hooks that send a request to my api to rerender my component with some fresh data.

I tryied a couple of things and I couldn't find a way to test it. Is it supported by this library?

Move to testing-library org and make scoped package

I could've sworn that this issue already existed, but I can't find it so here it is. I would like to formally invite you to bring this package into the testing-library org on GitHub and publish to the @testing-library scope under @testing-library/react-hooks.

I've sent you the invites etc. and you can learn more about the process of publishing to the scope here: testing-library/dom-testing-library#260 (comment)

Let me know if you have any questions or need assistance! Thanks!

Suggest not to lock @types/react

What is your question:

as this library locked version of @types/react, I expect we will keep seeing this kind of error as @types/react export global type
Screen Shot 2019-06-22 at 8 42 45 AM

It will be very annoying cause user need to sync the @types/react version with this library in order to avoid this error

could we use ^16.8.22 instead?

Testing the hook that uses async / await

I created a custom react hook that is supposed to handle all less important api requests, which i don't want to store in the redux state. Hook works fine but I have trouble testing it. My test setup is jest and enzyme, but I decided to give a try react-hooks-testing-library here as well.

What I have tried so far is to first mock fetch request with a fetch-mock library, what works fine. Next, i render hook with renderHook method, which comes from react-hooks-testing-library. Unfortunately, looks like I do not quite understand the waitForNextUpdate method.

This is how my hook looks like.

callApi method

/**
 * Method to call api.
 *
 * @param {HttpMethod} method - Request type.
 * @param {string} path - Restful endpoint route.
 * @param {any} body - Request body data.
 */
export const callApi = async (method: HttpMethod, path: string, body: any = null) => {
	// Sends api request
	const response = await sendRequest(method, path, body);
	// Checks response and parse to the object
	const resJson = response ? await response.json() : '';

	if (resJson.error) {
		// If message contains error, it will throw new Error with code passed in status property (e.g. 401)
		throw new Error(resJson.status);
	} else {
		// Else returns response
		return resJson;
	}
};

useApi hook

export function useApi<R, B = undefined>(
	path: string,
	body: B | undefined = undefined,
	method: HttpMethod = HttpMethod.GET
): ResponseStatus<R> {
	const [response, setResponse] = useState();
	const [isLoading, setIsLoading] = useState<boolean>(false);
	const [error, setError] = useState<string | boolean>(false);

	useEffect(() => {
		const fetchData = async (): Promise<void> => {
			setError(false);
			setIsLoading(true);
			try {
				const result = await callApi(method, path, body);
				setResponse(result);
			} catch (errorResponse) {
				setError(errorResponse);
			}

			setIsLoading(false);
		};

		fetchData();
	}, [path, body, method]);

	return { response, isLoading, error };
}

Hook can take 3 different state combinations that I would like to test. Unfortunately, I have no idea how.

Loading data:

{ response: undefined, isLoading: true, error: false }

Loaded data:

{ response: R, isLoading: false, error: false }

Error:

{ response: undefined, isLoading: false, error: true }

This is how my test looks like at this moment:

import fetchMock from 'fetch-mock';
import { useApi } from './hooks';
import { renderHook } from '@testing-library/react-hooks';

test('', async () => {
	fetchMock.mock('*', {
		returnedData: 'foo'
	});

	const { result, waitForNextUpdate } = renderHook(() => useApi('/data-owners'));

	console.log(result.current);

	await waitForNextUpdate();

	console.log(result.current);
});

I reviewed a lot of articles on the medium and issues on github, but I still can't see where the problem may be.

How to test context with useReducer

The following are my React Context with useReducer implementation. I want to test my consumer. But It does not work. Would you please help me figure out my mistakes. Thank you!

CountInterface.test.js

import React from 'react';
import { render, cleanup } from '@testing-library/react';
import { renderHook, act } from '@testing-library/react-hooks';

import 'jest-dom/extend-expect';

import { CountContext, CountProvider, useCountContext, countReducer } from '../CountContext';
import PlusMinus from '../CountInterface';

afterEach(cleanup)

test('NameConsumer shows value from provider', () => {
const initialState = {count: 0};
const { result } = renderHook(() => React.useReducer(countReducer, initialState));
const [myState, dispatch] = result.current;

const tree = (
    <CountContext.Provider value={[myState, dispatch]} >
        <PlusMinus />
    </CountContext.Provider>
);
const { getByText, getAllByTestId, getByTestId, container } = render(tree);

const mybutton = getByText(/Sub/i);

try {
    act(() => {
        dispatch({ type: "INCREASE"});
    });
} catch (e) {
}

// I tried to watch the console.log in the CountInterface.js, but I do not see and log printing.
// So it means the dispatched action was not updating the state in the consumer component.
// Can someone help me figure it out on how to do my test program for the CountInterface.js
// component? I want to dispatch an action and check the status of state/count value.
})


setupTests.js
import '@testing-library/react/cleanup-after-each'
import 'jest-dom/extend-expect'


App.js
import React from 'react';
import PlusMinus from './CountInterface';
import { CountProvider } from './CountContext'

const App = () => {
return (






);
}

export default App;

CountContext.js
import React from 'react'

const countReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case "INCREASE":
console.log("To increase")
return { count: state.count + 1 };
case "DECREASE":
console.log("To decrease")
return { count: state.count - 1 };
default:
return state
}
}

const CountContext = React.createContext({ count: 0 });

const CountProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(countReducer, { count: 0 });

return (
    <CountContext.Provider value={[state, dispatch]}>
        {children}
    </CountContext.Provider>
)

}

const useCountContext = () => {
const context = React.useContext(CountContext)
if (context === undefined) {
throw new Error('useCountState must be used within a CountProvider')
}
return context
}

export { CountContext, CountProvider, useCountContext, countReducer }


CountInterface.js
import React from 'react';
import { useCountContext } from './CountContext';

const PlusMinus = (props) => {
const [state, dispatch] = useCountContext();

const onAdd = (e) => {
    dispatch({ type: "INCREASE" });
}

const onSub = (e) => {
    dispatch({ type: "DECREASE" });
}

console.log(11111111111,  state.count);

return (
    <div>
        <span>Count: {state.count} </span>
        <div>
            <button onClick={onAdd}>Add</button>
            <button onClick={onSub}>Sub</button>
        </div>
    </div>
);

}

export default PlusMinus;

regression: console.error supression no longer works in 0.5

  • react-hooks-testing-library version: 0.5.0
  • react-testing-library version: 6.1.2
  • react version: 16.8.6
  • node version: 11.10
  • npm (or yarn) version: 1.15.2

Relevant code or config:

facebook/react#11098 (comment)

let your = (code, tell) => `the ${story}`;

What you did:

function onError(e: any) {
  e.preventDefault();
}
beforeEach(() => {
  window.addEventListener('error', onError);
});
afterEach(() => {
  window.removeEventListener('error', onError);
});

What happened:

console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9215
The above error occurred in the component:
in TestHook
in Suspense
in ErrorBoundary
in Suspense (created by wrapper)
in ExternalCacheProvider
in Unknown (created by wrapper)
in wrapper

React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.

Reproduction:

Problem description:

This used to suppress the console errors from things thrown in render being caught by errorboundaries. It doesn't work anymore

Suggested solution:

(0 , _reactTestRenderer.act) is not a function

  • react-hooks-testing-library version: 0.5.0
  • react-testing-library version: 16.8.3
  • react version: 16.8.2
  • node version: 10.15.1
  • npm (or yarn) version: 1.13.0

Relevant code or config:

  it('allows to upload photos through the mutation', async () => {
    const { result, nextUpdate } = renderHook(() =>
      usePhotosUpload({
        photosUploadMutation,
      }),
    );

    act(() => {
      result.current.onPhotoUpload({
        files: [file],
        accountId: 'my-account',
      });
    });

    await nextUpdate();

    expect(photosUploadMutation).toHaveBeenCalled();
  });

What you did:

Run yarn test

What happened:

TypeError: (0 , _reactTestRenderer.act) is not a function

      13 | 
      14 |   it('allows to upload photos through the mutation', async () => {
    > 15 |     const { result, nextUpdate } = renderHook(() =>
         |                                    ^
      16 |       usePhotosUpload({
      17 |         photosUploadMutation,
      18 |       }),

      at renderHook (../../../node_modules/react-hooks-testing-library/lib/index.js:151:30)
      at Object._callee$ (__test__/hooks/usePhotosUpload.test.js:15:36)
      at tryCatch (../../../node_modules/regenerator-runtime/runtime.js:62:40)
      at Generator.invoke [as _invoke] (../../../node_modules/regenerator-runtime/runtime.js:296:22)
      at Generator.prototype.(anonymous function) [as next] (../../../node_modules/regenerator-runtime/runtime.js:114:21)
      at asyncGeneratorStep (__test__/hooks/usePhotosUpload.test.js:13:103)
      at _next (__test__/hooks/usePhotosUpload.test.js:15:194)
      at __test__/hooks/usePhotosUpload.test.js:15:364
      at Object.<anonymous> (__test__/hooks/usePhotosUpload.test.js:15:97)

Problem description:

Getting the error above whenever I try to use renderHook.

Add global cleanup

I love react-testing-library and would be interesting if react-hooks-testing-library have the same clean up behavior.

So besides this

import { cleanup } from 'react-hooks-testing-library';

afterEach(cleanup);

We could have one more way to do that and use the same approach as react-testing-library and use it in setup files

import 'react-hooks-testing-library/cleanup';
// or
import 'react-hooks-testing-library/cleanup-after-each';

The implementation could be the same as react-testing-library

// cleanup-after-each.js or just cleanup.js
afterEach(require('./dist').cleanup);

// to make sense to react-hooks-testing-library just change to /lib
afterEach(require('./lib').cleanup);

So the test suites would be more cleaner and you don't have to worry about cleanups. I would love to open a PR for this. 😁

Add Usage docs to README

The usage of renderHook, the values it returns and the available options need to be added to the README (there is a TODO section for it already.

Duplicated identifier Errors with TypeScript

  • react-hooks-testing-library version: 1.0.4
  • react-test-renderer version: 16.8.6
  • react version: 16.8.17
  • @types/react version: 16.8.17
  • typescript version: 3.3.3333
  • node version: 11.9.0
  • npm version: 6.7.0

Relevant code or config:

-

What you did:

I installed the latest Version of react-hooks-testing-library and got after a typecheck the following error.

What happened:

node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts:2817:14 - error TS2300: Duplicate identifier 'LibraryManagedAttributes'.

2817         type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
                  ~~~~~~~~~~~~~~~~~~~~~~~~

  node_modules/@types/react/index.d.ts:2816:14
    2816         type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
                      ~~~~~~~~~~~~~~~~~~~~~~~~
    'LibraryManagedAttributes' was also declared here.

node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts:2881:13 - error TS2717: Subsequent property declarations must have the same type.  Property 'iframe' must be of type 'DetailedHTMLProps<IframeHTMLAttributes<HTMLIFrameElement>, HTMLIFrameElement>', but here has type 'DetailedHTMLProps<IframeHTMLAttributes<HTMLIFrameElement>, HTMLIFrameElement>'.

2881             iframe: React.DetailedHTMLProps<React.IframeHTMLAttributes<HTMLIFrameElement>, HTMLIFrameElement>;
                 ~~~~~~

node_modules/@types/react/index.d.ts:2816:14 - error TS2300: Duplicate identifier 'LibraryManagedAttributes'.

2816         type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
                  ~~~~~~~~~~~~~~~~~~~~~~~~

  node_modules/@testing-library/react-hooks/node_modules/@types/react/index.d.ts:2817:14
    2817         type LibraryManagedAttributes<C, P> = C extends React.MemoExoticComponent<infer T> | React.LazyExoticComponent<infer T>
                      ~~~~~~~~~~~~~~~~~~~~~~~~
    'LibraryManagedAttributes' was also declared here.

Reproduction:

Problem description:

The issue is that I use the @types/react: 16.8.17 while this library uses @types/react: 16.8.22 which results in 2 different installed @types/react versions. After updating to the latest @types/react the problem was solved.

Suggested solution:

There should be any typescript dependency installen when I install this package.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.