test-them-all
v3.0.0
Published
Solution for easy integration/end-to-end test with React
Downloads
11
Readme
test-them-all
My quest to make integration-system-functional test with React easy. Hopefully it'll make sense for someone !
React router v4 : test-them-all v3 and up for React router v3 : test-them-all v2 React router v2 : test-them-all v2
Table of Contents generated with DocToc
- Why ?
- How to install ?
- How to use this thing ?
- API
- Troubleshooting
- Feedbacks... Contributions...
Why ?
- Simplify and uniformize tests
- Remove boiler plate
- Improve the feedback loop through logging:
- Sync errors
- Errors in
asyncAction
's - Errors in React component lifecycle methods
state
andprops
of the relevant component (in debug mode ofasyncAction
's)
How to install ?
npm install -D test-them-all
Needless to say that you need to have react
installed. You also need:
reat-dom
react-addons-test-utils
react-router
(Optional: only if you want to usewaitRoute
)
You will need 3 other things to setup:
- A polyfill
- Import
test-them-all
before anything else - Set a global
afterEach
to reset the DOM state
Polyfill
As any user of many ES6+ goodies, a polyfill is needed. I suggest babel-polyfill
:)
npm install -D babel-polyfill
I recommend to include the polyfill in the test command. If you code in ES5 and don't use any transpiler, I would use something like:
mocha test/end_to_end/ -r babel-polyfill -r test-them-all --recursive
Why to require it in the mocha call ?
To use unexpected-react
a special setup with a specific order of require/import is required. This
is done for you but you need to make sure that react
is not imported before test-them-all
.
I just find it way less trouble to import it in the test call.
Resetting the fake DOM state
You just need to have a global afterEach
hook. To achieve that, you can simply create a file like any other of your test file and add this in it. The file has to be in your test folder and fit the regex you might use to filter the test files so that it will be considered by mocha.
/* global afterEach */
import {
unmountApp
} from 'test-them-all'
afterEach(unmountApp)
How to use this thing ?
asyncIt('displays the current count', async () => {
const renderedComponent = mountApp(Test)
await new AsyncAction()
.listenOn(renderedComponent)
.trigger(renderedComponent.asyncIncrease)
.waitState((state) => state.count === 1)
expect(renderedComponent, 'to contain', <p>1</p>)
})
If you don't need to test the component in between two change, you can use the action without a trigger. I personally prefer always using the trigger to have the action in one statement/block except when the trigger is the initial render (for async fetch in componentWillMount
for instance).
asyncIt('displays succeeded when the asyncAction is successful', async () => {
const renderedComponent = mountApp(Test)
await new AsyncAction()
.listenOn(renderedComponent)
.waitState((state) => state.name === 'some fetched name')
expect(renderedComponent, 'to contain', <h1>some fetched name</h1>)
})
Used to test this component:
/* Nothing special with the component... just here as context for the test... */
class Test extends React.Component {
constructor () {
super()
this.state = {
actionCount: 0,
name: 'foo'
}
this.increase = this.increase.bind(this)
}
async componentWillMount () {
this.state.name = await fetchName() // will return 'some fetched name'
}
async asyncIncrease () {
const count = this.state.count + 1
try {
await asyncDarkMagic()
this.setState({
count
})
} catch (err) {
// do something else
}
}
render () {
return (
<h1>{this.state.name}</h1>
<p>{this.state.counter}</p>
)
}
}
API
actionLogger
Object with 2 methods. Start and stop. I like it easy, what d'you think !? :)
start
Starts the action logging globally (until actionLogger.stop()
is called). Useful mostly for mocha's hooks such as before
,beforeEach
, after
and so on.
stop
Well, I'm sure you get it ;)
asyncIt (description, test, config)
The only thing that it does is making sure that the fake DOM is cleaned after the test, whether it succeeded or failed.
Oh ! It also sets the action logging for the test scope when config === 'debug'
.
Use it exactly like the it
in mocha. asyncIt.only
and asyncIt.skip
work the same as well.
***Attention ! Unlike mocha
, there's no done parameter. You have to whether make sure your test is over when it returns or returns a promise.
mountApp (RootComponent, props)
RootComponent
The class or function, not the JSX as with it would be with renderIntoDocument. It just wraps the lifecycle method of the component and keeps a reference of the the app for the DOM cleaning and to get a hand on the Router. That simple !
export const mountApp = (RootComponent, props) => {
wrapLifecycleMethodsWithTryCatch(RootComponent) // from react-component-errors
renderedApp = renderIntoDocument(<RootComponent {...props} />)
return renderedApp
}
AsyncAction
class
Some sort of builder simplifying async action in our test.
constructor new AsyncAction(actionDescription)
actionDescription
is gonna be shown when logging the actions for debugging.
listenOn (renderedComponent)
Tell the action which renderedComponent
to listen to the updates. This will be ignore if when using waitRoute
.
Return this
.
trigger (actionFunction)
actionFunction
Should trigger a reaction that'll make the app/component get to th wanted state
eventually. Otherwise, the test will hang within this AsyncAction
.
Return this
.
waitProps (testFunction)
testFunction (props)
The AsyncAction
is checking after every render of the component provided with listenOn
if testFunction
returns true to resolve.
The props
of the component provided with listenOn
is passed to testFunction
as parameter.
waitState (testFunction)
testFunction (state)
The AsyncAction
is checking after every render of the component provided with listenOn
if testFunction
returns true to resolve.
The state
of the component provided with listenOn
is passed to testFunction
as parameter.
waitRoute (targetRoutePathOrTestFunction)
testFunction (route)
####### route
being a string represention location.pathname
To simplify useless overwork, a special method has been added to wait for a route transition when using react-router
.
debug (debugFunction)
console.log
the state
and the props
of the component that is listened to after every render of the given component in listenOn
.
In the case of waitRoute
, only the pathname
of the current location is logged.
If you want to use a custom function, you can always do it via debugFunction(component)
. You can access the props with component.props
and the state with component.state
.
Return this
.
expect
It is almost the same expect of unexpected-react but it allows batch testing. Just pass an array as the last parameter like so:
expect(renderedComponent, 'to contain', [
<li>foo</li>,
<li>bar</li>
])
Moreover, the setup is already made for you. See unexpected-react for more details.
Troubleshooting
The test pass when it should not ?
Make sure your test is over when it returns or otherwise you have to return a promise.
Simulate.click(linkComponent)
does not do anything
If you Simulate Clicks on a Link, you have to pass at least the event: { button: 0 }
like so:
Simulate.click(linkComponent, { button: 0 })
Feedbacks... Contributions...
Highly appreciated ! I'm unfortunately not perfect yet.
For pull requests,
Just make sure to add tests with your work.
Oh, oh ! Why not npm run lint
as well :)