Tests are code too

I’m going to do an experiment on a branch to see if I can make the interface for testing a little easier to read. I want something a bit more like assert_select in Rails.

Let’s see now. Pretending it’s magic…

  test('shows the first blog post', async () => {
    const component = await mount(<Application />)
    
    check(component).has(
        {
          '.site-name':   'Blogging', 
          '.post .title': 'React on Rails', 
          '.post .body':  'I can use React with Rails.', 
        }
    )
  })

Or, how about this?

  test('also shows the first blog post', async () => {
    const component = await mount(<Application />)
    assert_select(component, '.site-name',   'Blogging')
    assert_select(component, '.post .title', 'React on Rails')
    assert_select(component, '.post .body',  'I can use React with Rails.')
  })

assert_select in Rails allows nesting. Maybe something like this?

 test('shows the first blog post with nesting', async () => {
    const component = await mount(<Application />)

    assert_select(component, '.site-name',   'Blogging')
    assert_select(component, '.post', match => {
      assert_select(match, '.title', 'React on Rails')
      assert_select(match, '.body', 'I can use React with Rails.')
    })
  })

Let’s try the simple (non-nested) version for a while to see how it feels. Here’s a quick implementation.

export function assert_select(component, selector, expectation=1) {
  const selected = component.find(selector)
  switch(typeof(expectation)) {
    case 'string':
      expect(selected.text()).toEqual(expectation)
      break
    case 'number':
      expect(selected.length).toEqual(expectation)
      break
    default:
      expect(expectation).toEqual('string or number')
      break
  }
}

If the expectation is a string, we’ll see if text matches. If it’s a number, we’ll check the number of matches. If you skip the expectation, we’ll just check that there is a match.

Let’s see how it goes. I’ll extract this into a ReactHelper along with a wrapper function for mount() that I will call display().

// test/javascript/helpers/ReactHelper.jsx

import {configure, mount} from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

configure({ adapter: new Adapter() })

export function display(component) {
  return mount(component)
}

This hides away the fiddly code to initialise enzyme and (SPOILERS) it also provides a handy place to initialise global state should we need that later.

I’ve updated all the tests to use assert_select and the setup code in ReactHelper.

Tidy is good.

Published by

Kevin

I've been coding professionally for 30 years, 25 of those in Silicon Valley. I'm home now in beautiful Bristol, England.

Leave a Reply

Your email address will not be published. Required fields are marked *