Configuring Jest for react

Jest does not handle JSX and some new JS features natively without a babel config. Also, enzyme or an equivalent is needed to render the components.

Installing Enzyme and the adapter

Install enzyme and the correct react version adapter:

npm install --save-dev enzyme enzyme-adapter-react-16  

How to set up jest and enzyme

Just need to make sure Jest is installed and is configured to run in package.json. I sometimes do a single run setup (test) and one where jest keeps running while watching for changes (test-watch).

// Config in package.json to set up correct babel env and 
// use jest --watch mode so that tests can re-run on changes
  "test": "BABEL_ENV=jest jest",
  "test-watch": "BABEL_ENV=jest jest --watch",

jest-emotion is also needed to check emotion styles

If using emotion styles, in order to check any of them in testing jest-emotion needs to be installed and then used to extend jest.

npm install --save-dev "jest-emotion"  

Checking emotion styling

Import it, extend the jest expect object, and then use it to check for style put in place with emotion.

import { matchers } from 'jest-emotion'  
expect.extend(matchers)  
expect(wrapper.find('rect').at(0)).toHaveStyleRule("transform", "translate(4px,0px)")  

This modifies jest expect; may change behavior

I was trying to use .toHaveStyleRule on a regular (non-emotion) style with jest/enzyme in a test file where I am using the emotion extension and it did not seem to work. It may be that with the emotion extension in place it can only check for emotion styles, which would be a bit annoying.

Setting up the babel configuration

This is specific to using React and Emotion, but for any React project the preset-react and preset-env would be in there. The target needs to be node because jest runs as a node program.

  "jest" : {
    "presets": [ 
      "@babel/preset-react", 
    [ "@babel/preset-env", 
    {  
      "targets": {
        "node": 8
      }
    }],
    ],
    "plugins": ["emotion"]
  }

Testing is the same as JS generally but also different

Because of the components you need a way to render components and then check if you got what you expected.

Try not to test implementation details

This means that instead of testing little details of how something works, try and test what it yields that will be seen by the user or that the developer will have to work with in the form of props/api.

Shallow vs mount with enzyme

There are different ways to render, these are the main ones.

Shallow

Render just the first level of the component tree, nothing more. If you just want to see if the top level components are successfully rendered this is the tool to use. It can also be useful when a component is linked to others but it is not practical to fully mount all of them.

Downsides of shallow render

You will only get the base html elements if that is what the first level of depth yields. Only useful to check if something works at top level, gets props.

Mount

Mount a component and all children to full depth, including anything else it pulls in. I use it most of the time to make sure that the actual low-level Html elements that form the basis for the component are rendered correctly.

Downsides of mount

It takes longer, and will mean that the test depends on any child working too. This make sense in a lot of cases to me though; if the child does not behave the expected way, then it did not work.

Getting summary of what was rendered with mount/shallow

wrapper.debug() - Very useful call for enzyme tests. Debug() lets you see what was rendered, which is really helpful at just about any point. Even when you know what it should be this gives the exact details.

Expect is still used, but on the wrapper output

Expect matchers are still used to determine test results, but they will run on wrapper function output. Here are a few examples of this:

  expect(wrapper.find('path').length).toEqual(77)
  expect(wrapper.find('GeoFeature').at(10)).toHaveStyleRule('stroke','#bcc6c6')

General Rule: don’t use simulate for anything

Simulate() is poo-pooed by the creators of react/enzyme in every github issue regarding it you will see. They indicate it will be removed at some point, and that it does not effectively simulate what the browser does, so just don’t use it.

What to do instead

Test the functionality of the code that responds to browser interaction directly. In this tutorial I will show several examples of this.

How the enzyme component matching works

There are several different functions and they do pretty different things, so yes it matters.

Contains

This does an exact match, so the element arguments and content need to match exactly.

expect(wrapper.contains([<text fill="#000" style={{dominantBaseline: 'mathematical'}}   
x={-6} dx={"-0.4em"}></text>])).toBeTruthy()  

containsMatchingElement

This does a partial match; the props are matched if you provide them, but only the contents need to match.

expect(wrapper.containsMatchingElement(<rect height={2} width={4} />)).toBeTruthy()  

containsAllMatchingElements

Matching rules are the same as containsMatchingElement; it does not need to be exact except in the contents, but it will match any props you give it. Matches all elements in the array, so if even one does not match, returns false.

expect(wrapper.containsAllMatchingElements([  
./src/components/__tests__/axes.test.js-      <line x2={10}/>,  
./src/components/__tests__/axes.test.js-      <text x={6} dx="0.5em"> 10   
</text>  
 ])).toBeTruthy()  

find()

Useful for quickly finding elements; can match element names or classes/ids. WIll find all the matching elements and return an array of them.

expect(wrapper.find('rect').at(1)).length).toEqual(5)  

.at() let you get a wrapper back

This solves the problem of doing find(”).get(0) where the next call needs the wrapper, since .get() returns the node. This is true with checking emotion styles for example.

expect(wrapper.find('rect').at(1)).toHaveStyleRule("transform","translate(10px,0px)")   

.hostNodes() does not worked the way I hoped

The documentation says it only returns HTML nodes (not custom components) but it does not do this recursively, so it does it only at the top level and removes HTML children if they are below a custom, but not nested custom components. It seemed like it would be very useful because I could just see the rendered html, but that is not how it works.

Simulating a generic child component for testing

This arrangement is for when a component takes child components as an argument but you don’t want them to do anything. I create a custom child that just outputs something generic like a <div/> with the main purpose being to check that the parent rendered the child or passed it the correct hooks/args.

The TestElem will get all the arguments, then you can check it

The nested div is just so that the return is valid and react does not complain; it does nothing.

const TestElem = (props) => {  
  return(  
    <div />  
  )
}

How to test hooks and state changes

Enzyme does not technically support Hooks, but it is using react to render, so it does not need to.

Use a hook wrapper that is just an elements using a hook

The work around is simple, you use the hook inside a generic test element whose purpose is just to run the hook. Once again the the returned html can be something generic like a <div /> because the point is to get the hooks attached to an element so that you can grab/call them to see if they are working right.

Set up a generic hook wrapper that takes hook as arg

The HookWrapper component takes a hook and calls it. Arguments can be set up before it is called.

const HookWrapper = (props) => {  
  let hook = props.hook ? props.hook() : undefined  
  return(  
    <div hook={hook} />  
  )
}

Then test the hook by finding it and making calls directly

The hook is set up as a function call because the HookWrapper actually calls it. This example works for the shallow() jest render case, which I cannot reccommend overall because of problems it tends to have updating the hook. It is however easier to use with hooks than mount(), so if I find ways to make it work more consistently, I would use it.

Pass the hook as a function

This is because the wrapper calls it to set it up.

  let wrapper = shallow(<HookWrapper hook={() => useSelect()}  />)   

Get the hook and check values/ make calls directly

Find the generic element set up in the wrapper and grab the hook from it using props() and .hook.

  let hook = wrapper.find('div').props().hook;  
  expect(hook.offx.current).toEqual(0)  
  hook.start([10, 15, 0.5, 2.0, 1, 2])  

Hooks with mount(); must re-get the hook each time

You have to find/get the hook after each operation if testing hooks with mount(). This assumes you are using mount() vs shallow(), I have found mount() much more reliable so I rarely use shallow() in spite of the extra code needed.
The pattern to use hooks is: find(), call the action, find() again, check the value.

  hook = wrapper.find('div').props().hook  
  hook.move([31, 12])  
  hook = wrapper.find('div').props().hook  
  expect(hook.width).toEqual(5)  

Using shallow vs mount for the hook; shallow seems unreliable

Shallow allows the hook to update without any find/act/find/check sequence, but it failed with useEffect(), and in many other harder to figure out cases, so I rarely use it.

But when using mount you need a find()/act()/update() sequence

I thought it was because the Hook was being called indirectly, but if you want to use mount, then see the instructions below for making sure everything updates.

Testing a hook being used by another component

In this case it is the same useSelect hook but I am testing to make sure it works hooked up to the mouse and touch handlers in SelectBox.

The hookwrapper passes the hook to the child

The child is what will be tested. It is a variant of the HookWrapper aimed at testing something that uses a hook. It passes the hook to the child component that is provided to it.

import { act } from 'react-dom/test-utils';  
import fps from '@jadesrochers/fpstreamline'  
const HookWrapper = (props) => {  
  let hook = props.hook ? props.hook() : undefined  
  const propsToChildren = R.map(child => {  
    return React.cloneElement(child,   
     {...props,  
      select: hook,   
      })
  })(fps.toArray(props.children))  

  return(  
    <div hook={hook} >  
      {propsToChildren}  
    </div>  
  )
}

Extract the functions used to call the hook from the wrapper

So in this instance I will find the element output by selectbox and grab the MouseDown/Move/Touch functions since they call the hook.

    let wrapper = mount(<svg>  
      <HookWrapper  hook={() => useSelect()} >  
        <SelectBox key='selectbox' width={100} height={100} />   
      </HookWrapper>  
    </svg>)   

Need a sequence of find() act() update() because mount is being used

This is true any time you use a HookWrapper and mount. I need to full render tree so there is no choice in this instance. Get the function so that it has the updated hook, wrap the call in act(() => {}) so that the hook changes will be handed, and then update the wrapper to reflect the changes from the hooks. ```javascript mouseup = wrapper.find(‘g’).props().onMouseUp;
act(() => {
mouseup()
}) wrapper.update()
// Repeat after tests as needed.


## Jest and Enzyme testing summary
Testing with jest and enzyme is a lot like doing any other jest tests; enzyme is present to do the React rendering but does not inherently change anything else about the testing process. When it comes to testing hooks and interative functionality, I hope that the helper functions I have shown for that here give you a better idea of how to do that while avoiding simulate(), which should not be used.