Find Nodes , Understand the Selectors, Test Enzyme

·

7 min read

Find Nodes , Understand the Selectors, Test Enzyme

Searching for Nodes

We can use built-in methods to search for certain nodes in our Shallow Rendered Component, and combine these methods with Jest assertions to make sure everything renders correctly.

In App.test.js, we define a search for a node on our Shallow Rendered Component with the .find() method, and wrap that in a Jest assertion, expect().toBe(). In this case, we search for a p element and expect it to be a length of 1:

describe('<App />', () => {
  const wrapper = shallow(<App />)
  it('should contain 1 p element', () => {
    expect(wrapper.find('p').length).toBe(1)
  })
})

Running our test script:

npm test

We can see that the test passes, and that our component contains just one p element.

In this next part, we use the class selector to make sure that an element with a specific class exists. If your App.js component is different than the instructor's, like mine is, you have to give your p element a class:

<p className="App-intro">
  Edit <code>src/App.js</code> and save to reload.
</p>

Now we define a search for an element with class "App-intro" and expect it to exist:

describe('<App />', () => {
  const wrapper = shallow(<App />)
  it('should contain 1 p element', () => {
    expect(wrapper.find('.App-intro').exists()).toBe(true)
  })
})

Looking back at our terminal, we should see that the test passes. To see this test fail, we can change '.App-intro' selector within the .find() method to something else.

Now we define a search for a ul element and expect 3 child elements. We can also change 'should contain 1 p element' to something more fitting, like 'should contain a ul element with 3 children li elements'.

describe('<App />', () => {
  const wrapper = shallow(<App />)
  it('should contain a ul element with 3 children li elements', () => {
    expect(wrapper.find('ul').children().length).toBe(3)
  })
})

Now we must create a ul element with 3 children li elements in our App.js component:

<img src={logo} className="App-logo" alt="logo" />
<ul>
  <li>Test 1</li>
  <li>Test 2</li>
  <li>Test 3</li>
</ul>
<p className="App-intro">
  Edit <code>src/App.js</code> and save to reload.
</p>

We should see that our test passes with our terminal in watch mode.

Adding an outlying li element to App.js:

<img src={logo} className="App-logo" alt="logo" />
<ul>
  <li>Test 1</li>
  <li>Test 2</li>
  <li>Test 3</li>
</ul>
<li>Test 3</li>
<p className="App-intro">
  Edit <code>src/App.js</code> and save to reload.
</p>

Doesn't change the pass from our test, as the test is only looking for children of the ul element.

We can also give a class to the ul element in App.js:

<ul className="tyler">
  <li>Test 1</li>
  <li>Test 2</li>
  <li>Test 3</li>
</ul>
<li>Test 3</li>
<p className="App-intro">
  Edit <code>src/App.js</code> and save to reload.
</p>

And define a test in App.test.js that searches for a ul element and expects it to have that class:

describe('<App />', () => {
  const wrapper = shallow(<App />)
  it('should contain ul with class tyler', () => {
    expect(wrapper.find('ul').hasClass('tyler')).toBe(true)
  })
})

Our terminal in watch mode should show this test passes.

For this next test, we need to add an h1 element with the text 'Welcome to React' to our App.js component:

<img src={logo} className="App-logo" alt="logo" />
<h1>Welcome to React</h1>
<ul className="tyler">
  <li>Test 1</li>
  <li>Test 2</li>
  <li>Test 3</li>
</ul>
<li>Test 3</li>
<p className="App-intro">
  Edit <code>src/App.js</code> and save to reload.
</p>

In our App.test.js, we search for the h1 element and expect the text to be 'Welcome to React'

describe('<App />', () => {
  const wrapper = shallow(<App />)
  it('should contain an h1 with text Welcome to React', () => {
    expect(wrapper.find('h1').text()).toBe('Welcome to React')
  })
})

Returning to our terminal in watch mode, we should see that the test passes.

Enzyme Selectors

Many methods in Enzyme accept selectors as arguments. There are 5 categories of selectors that are accepted in Enzyme.

1. Valid CSS selectors include:

Element Syntax:

expect(wrapper.find('h1').text()).toBe('Welcome to React')

Class Syntax:

expect(wrapper.find('.tyler').text()).toBe('Welcome to React')

ID Syntax:

expect(wrapper.find('#tyler').text()).toBe('Welcome to React')

Attribute Syntax:

expect(wrapper.find('[href="tyler"]').text()).toBe('Welcome to React')

Combination of above:

expect(wrapper.find('a[href="tyler"]').text()).toBe('Welcome to React')

Contextual Selectors (>, +, ~):

expect(wrapper.find('[href="tyler ~ .clark"]').text()).toBe('Welcome to React')

2. Prop Selectors

Now we're gonna take a look at prop selectors.

In app.js, we create a Title component that returns a div with a text prop, then we add it to our App component:

const Test = () => <div>Testing</div>

// Title Component:
const Title = ({text}) => <div>{text}</div>

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h1>Welcome to React</h1>
        <ul className="tyler">
          <li>Test 1</li>
          <li>Test 2</li>
          <li>Test 3</li>
        </ul>
        <li>Test 3</li>
        <p className="App-intro">
          Edit <code>src/App.js</code> and save to reload.
        </p>
        // Add title component to App component:
        <Title text="Some title" />
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
      <Test />
    </div>
  );
}

In App.test.js, we can select our Title component by its text prop using an attribute selector:

expect(wrapper.find('[text="Some title"]').text()).toBe('Welcome to React')

3. Referencing Component Constructor

We can pass in a function that replicates the component we're trying to find as a selector:

expect(wrapper.find(function App() { return ... }).text()).toBe('Welcome to React')

4. Referencing Component displayName

To select a component with a displayName, simply pass in a string of that displayName as a selector:

expect(wrapper.find('App').text()).toBe('Welcome to React')

5. Object Property Selector

We can use the object property selector to find nodes by passing in an object that matches the property of a node as a selector. As demonstrated in the video, the following could select an img element with an alt tag of 'logo':

expect(wrapper.find({alt: 'logo'}).text()).toBe('Welcome to React')

Undefined properties are not allowed in the object property selector, and will cause error.

Test UI of a component

With unit tests, "we could test that the logo and title are rendered correctly inside of this component, but how could we make sure that the logo is above the h1 in hierarchy?"

In this lesson, we will create Jest snapshots in order to make sure that our rendered output remains consistent.

Navigate to App.test.js and write a new test:

describe('<App />', () => {
  const wrapper = shallow(<App />)
  it('should contain an element with logo as alt tag', () => {
    expect(wrapper.find({alt: 'logo'}).exists()).toBe(true)
  })
  // New Test:
  it('matches the snapshot', () => {
    const tree = shallow(<App />)
    expect(tree).toMatchSnapshot()
  })
})

Now, we run our test in the terminal:

npm test

And we should see that our test passes, and that a snapshot has been written.

Our new snapshot can be found at src/__snapshots__/App.test.js.snap. If we navigate there, you'll either see what the instructor sees in the video, or you'll see this:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<App /> matches the snapshot 1`] = `ShallowWrapper {}`;

The example shown above is what is generated in newer versions of Jest. In either case, it's easy to understand that our snapshot is hard to read.

To solve this problem, we need to add a serializer.

In the terminal, we install enzyme-to-json as a Dev Dependency:

npm install --save-dev enzyme-to-json

Inside of our App.test.js, we import from our new package:

// imports
import toJson from 'enzyme-to-json'

//...

And we pass our 'tree' into toJson():

it('matches the snapshot', () => {
  const tree = shallow(<App />)
  expect(toJson(tree)).toMatchSnapshot()
})

Now, on returning to our terminal, we can see that our snapshots are working. Our latest test has failed because the JSON rendered component does not match our original component.

In order to update our snapshot with a change we want to keep, we can press 'u' in the terminal.

Now, if we make a change in our App.js component, such as adding

Hello World!

, our test will fail because the component no longer matches that which is defined in the snapshot.

Again, to update our snapshot with a change we want to keep, we press 'u' in the terminal.

Resources