Skip to content

Instantly share code, notes, and snippets.

@chrisregner
Last active May 8, 2018 20:30
Show Gist options
  • Save chrisregner/ff0c65bb9786b0037f735e965cd47170 to your computer and use it in GitHub Desktop.
Save chrisregner/ff0c65bb9786b0037f735e965cd47170 to your computer and use it in GitHub Desktop.
A function which greatly reduces the boilerplate when testing the inner structure of a React component given a certain set of props/state. It uses Enzyme and Chai's assert (can be replaced with any other assertion library)
import { assert } from 'chai'
import { curry, forEachObjIndexed } from 'ramda'
const find = (wrapper, matcher) =>
wrapper.findWhere(x =>
x.prop('data-test') &&
x.prop('data-test')
.trim()
.split(' ')
.includes(matcher)
)
/*
// sample usage:
const MyCmpt = () =>
<div>
<div data-test='foo'>FOO!</div>
<div data-test='bar baz'>BAR! BAZ?</div>
</div>
const findInMyCmpt = findTestComponent(shallow(<MyCmpt />))
findInMyCmpt('foo') // returns wrapper for <div data-test='foo'>FOO!</div>
findInMyCmpt('baz') // returns wrapper for <div data-test='bar baz'>BAR! BAZ?</div>
*/
export const findTestComponent = curry(
(wrapper, matcher) => {
if (typeof matcher === 'string')
return find(wrapper, matcher)
if (Array.isArray(matcher))
return matcher.reduce(
(currentWrapper, matcher) => find(currentWrapper, matcher),
wrapper
)
}
)
/*
// sample usage:
testSubComponents(postPage, {
// assert that 3 element that matches [data-test='post-item'] is rendered and...
'post-item': [3, (postItems) => {
// assert that each post item renders the correct data
postItems.forEach((postItem, i) => {
assert.equal(postItem.text(), posts[i],
`expected post item at index ${i} to render ${posts[i]}`)
})
}],
// assert that 1 element that matches [data-test='load-more-btn'] is rendered and...
'load-more-btn: [1, (loadMore) => {
// assert that correct handler is called when it is clicked
assert.equal(handleLoadMore.getCallCount(), 0,
'expected handleLoader to have been called 0 times before clicking the button')
loadMore.simulate('click')
assert.equal(handleLoadMore.getCallCount(), 1,
'expected handleLoader to have been called 1 time after clicking the button')
}],
// assert that none of the following component is rendered
'loader': 0,
'error': 0,
'write-your-first-post': 0,
})
*/
export const testSubComponents = (wrapper, expectations) => {
forEachObjIndexed((expectation, matcher) => {
const componentCount = typeof expectation === 'number' ? expectation : expectation[0]
const customCondition = typeof expectation === 'number' ? () => {} : expectation[1]
const subwrapper = find(wrapper, matcher)
customCondition(subwrapper)
assert.equal(
subwrapper.length,
componentCount,
`expected ${componentCount} ${matcher} to be rendered`
)
}, expectations)
}
@chrisregner
Copy link
Author

To put in another way, it reduces this kind of tests:

describe('PostListPage', () => {
  const requiredProps = { /*some props that component requires */ }

  context('there is no post', () => { /* ... */ })
  context('there is no post and is loading', () => { /* ... */ })
  context('there is error', () => { /* ... */ })

  context('there are posts and is loading', () => {
    it('should render the posts', () => {
      const wrapper = shallow(
        <PostListPage
          posts={['post one', 'post two', 'post three']}
          {...requiredProps}
        />
      )

      assert.equal(wrapper.find('.post-item').length, 3)
    })

    it('should render the loader', () => {
      const wrapper = shallow(
        <PostListPage
          posts={['post one', 'post two', 'post three']}
          {...requiredProps}
        />
      )

      assert.equal(wrapper.find('.loader').length, 1)
    })

    it('should NOT render the error', () => {
      const wrapper = shallow(
        <PostListPage
          posts={['post one', 'post two', 'post three']}
          {...requiredProps}
        />
      )

      assert.equal(wrapper.find('.loader').length, 1)
    })

    // assert that 'load-more' button isn't rendered
    // assert that 'write your first post' button isn't rendered
  })
})

to this:

describe('PostListPage', () => {
  const requiredProps = { /*some props that component requires */ }

  it('should render the correct components when there is no post', () => { /* ... */ })
  it('should render the correct components when there is no post and is loading', () => { /* ... */ })
  it('should render the correct components when there is error', () => { /* ... */ })
  it('should render the correct components when there are posts and is loading', () => {
    const props = {
      ...requiredProps,
      posts: ['post one', 'post two', 'post three'],
      isLoading: true,
    }

    const wrapper = shallow(<PostListPage {...props} />)

    testSubComponents(wrapper, {
      'post-item': 3,
      'loader': 1,
      'error': 0,
      'load-more-btn: 0,
      'write-your-first-post': 0,
    })
  })
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment